package com.nimblestorage.nimbus.client.widgets.product.manage.initiator.initiatorgroup;
/*
 * Copyright 2012, Nimble Storage, Inc.
 */

import java.util.ArrayList;
import java.util.List;

import com.google.gwt.event.shared.HandlerRegistration;
import com.smartgwt.client.core.Rectangle;
import com.smartgwt.client.types.Alignment;
import com.smartgwt.client.types.Overflow;
import com.smartgwt.client.util.Page;
import com.smartgwt.client.widgets.Canvas;
import com.smartgwt.client.widgets.Label;
import com.smartgwt.client.widgets.events.ClickEvent;
import com.smartgwt.client.widgets.events.ClickHandler;
import com.smartgwt.client.widgets.events.MouseOutEvent;
import com.smartgwt.client.widgets.events.MouseOutHandler;
import com.smartgwt.client.widgets.events.MouseOverEvent;
import com.smartgwt.client.widgets.events.MouseOverHandler;
import com.smartgwt.client.widgets.form.DynamicForm;
import com.smartgwt.client.widgets.form.fields.FormItem;
import com.smartgwt.client.widgets.form.fields.events.BlurEvent;
import com.smartgwt.client.widgets.form.fields.events.BlurHandler;
import com.smartgwt.client.widgets.form.fields.events.FocusEvent;
import com.smartgwt.client.widgets.form.fields.events.FocusHandler;
import com.smartgwt.client.widgets.layout.HLayout;
import com.smartgwt.client.widgets.layout.Layout;
import com.smartgwt.client.widgets.layout.LayoutSpacer;
import com.smartgwt.client.widgets.layout.VLayout;

public class TstTooltipFactory {

    static {
    }

    // NOTE: these two layouts should not be directly accessed: use the
    // ParentLayout object
    private static Layout mParentLayout = null;
    private static Layout mChildLayout = null;

    /**
     * Identify the parent page containing the components (or window container
     * of the components) to validate.
     *
     * @param page
     */
    public static void setParentLayout(final Layout page) {
        assert (page != null);
        mParentLayout = page;
        mChildLayout = null;
    }

    public static void setChildLayout(final Layout layout) {
        assert (layout != null);
        assert (mParentLayout != null);
        mChildLayout = layout;
    }

    /**
     * Private interface shared by multiple classes in the file.
     */
    private interface IValidationParentLayout {
        public void addChild(Canvas canvas);

        public Rectangle computeFieldPageRect(
                List<IValidationComponent> formItems);

        public Rectangle getContainerPageRect();
    }

    private static class ProxyParentLayout implements IValidationParentLayout {

        private IValidationParentLayout mParentLayout = null;

        private final IValidationParentLayout getParentLayout() {
            if (this.mParentLayout == null) {
                this.mParentLayout = new ProxyParentLayout.ParentLayout(
                        TstTooltipFactory.mParentLayout, mChildLayout);
            }
            return mParentLayout;
        }

        ProxyParentLayout() {
        }

        @Override
        public void addChild(Canvas canvas) {
            final IValidationParentLayout parentLayout = getParentLayout();
            parentLayout.addChild(canvas);
        }

        @Override
        public Rectangle computeFieldPageRect(
                List<IValidationComponent> formItems) {
            final IValidationParentLayout parentLayout = getParentLayout();
            return parentLayout.computeFieldPageRect(formItems);
        }

        @Override
        public Rectangle getContainerPageRect() {
            final IValidationParentLayout parentLayout = getParentLayout();
            return parentLayout.getContainerPageRect();
        }

        static class ParentLayout implements IValidationParentLayout {

            private final Layout mParentLayout;
            private final Layout mChildLayout;

            ParentLayout(final Layout page, final Layout dialog) {
                assert (page != null);
                mParentLayout = page;
                mChildLayout = dialog;
            }

            @Override
            public void addChild(Canvas canvas) {
                final Layout layout = (mChildLayout != null) ? mChildLayout
                        : mParentLayout;
                layout.addChild(canvas);
            }

            @Override
            public Rectangle computeFieldPageRect(
                    List<IValidationComponent> formItems) {
                Rectangle validationFieldPageRect = null;
                for (final IValidationComponent formItem : formItems) {
                    final Rectangle rectangle = formItem.getFormPageRect();
                    if (validationFieldPageRect != null) {
                        validationFieldPageRect = union(rectangle,
                                validationFieldPageRect);
                    } else {
                        validationFieldPageRect = rectangle;
                    }
                }
                if (mChildLayout != null && validationFieldPageRect != null) {
                    final Rectangle childRectangle = mChildLayout.getPageRect();
                    final Rectangle r = offset(validationFieldPageRect,
                            childRectangle);
                    return r;
                } else {
                    return validationFieldPageRect;
                }
            }

            @Override
            public Rectangle getContainerPageRect() {
                final Layout layout = (mChildLayout != null) ? mChildLayout
                        : mParentLayout;
                return layout.getPageRect();
            }

            private final Rectangle offset(Rectangle pageRect,
                    Rectangle offsetRectangle) {
                final Rectangle r = new Rectangle(pageRect.getLeft()
                        - offsetRectangle.getLeft(), pageRect.getTop()
                        - offsetRectangle.getTop(), pageRect.getWidth(),
                        pageRect.getHeight());
                return r;
            }

            private final Rectangle union(final Rectangle r1, final Rectangle r2) {
                final int x1 = Math.min(r1.getLeft(), r2.getLeft());
                final int y1 = Math.min(r1.getTop(), r2.getTop());
                final int x2 = Math.max(r1.getLeft() + r1.getWidth(),
                        r2.getLeft() + r2.getWidth());
                final int y2 = Math.max(r1.getTop() + r1.getHeight(),
                        r2.getTop() + r2.getHeight());
                final Rectangle r = new Rectangle(x1, y1, x2 - x1, y2 - y1);
                return r;
            }
        }
    }

    public static AbstractValidationWrapper getValidationWrapper(
            final Canvas canvas) {
        canvas.setHeight100();
        canvas.setWidth100();

        final IValidationParentLayout parentLayout = new ProxyParentLayout();
        final AbstractValidationWrapper validationWrapper = new ValidationWrapper(
                parentLayout, canvas);
        return validationWrapper;
    }

    public static AbstractValidationWrapper getValidationWrapper(
            final FormItem formItem) {
        final DynamicForm form = new DynamicFormForTestAutomation();
        shrinkWrap(form);
        form.setFields(formItem);
        return getValidationWrapper(form);
    }

    public static abstract class AbstractValidationWrapper extends HLayout implements
            IValidationField {
    }

    private static class ValidationWrapper extends AbstractValidationWrapper implements
            BlurHandler,
            ClickHandler,
            FocusHandler,
            IValidationField,
            MouseOutHandler,
            MouseOverHandler {

        private static final int VALIDATION_ERROR_BORDER_WIDTH = 2;

        private final IValidationParentLayout mContainingPage;
        private List<IValidationComponent> mValidationComponents = null;
        private final Canvas mContentToWrap;
        private ToolTipCanvas mToolTipCanvas = null;
        private String mTooltipMessageText = null;
        private boolean mTooltipHandlerEnabled = false;

        // mToolTipId -- non-null means a tooltip was or is displayed
        private static String mToolTipId = null;

        // mToolTipRefCount -- > zero means a tool tip is displayed
        private static int mToolTipRefCount = 0;

        ValidationWrapper(final IValidationParentLayout parentLayout,
                final Canvas contentToWrap) {
            mContainingPage = parentLayout;
            mContentToWrap = contentToWrap;
            this.setBorder(VALIDATION_ERROR_BORDER_WIDTH + "px transparent");
            this.addMouseOutHandler(this);
            this.addMouseOverHandler(this);
            contentToWrap.addMouseOutHandler(this);
            contentToWrap.addMouseOverHandler(this);
            this.setMembers(contentToWrap);
        }

        /**
         * Get list of fields provided validation support by this wrapper. Defer
         * calling this method until wrapped component, mContentToWrap, has been
         * constructed.
         *
         * @return list of form items for which to provide validation support.
         */
        private final List<IValidationComponent> getValidationComponents() {
            if (mValidationComponents == null) {
                mValidationComponents = identifyFormItems(mContentToWrap);
                for (final IValidationComponent formItem : mValidationComponents) {
                    if (formItem.doSupportValidation()) {
                        formItem.addFocusHandler(this);
                        formItem.addBlurHandler(this);
                    }
                }
            }
            return mValidationComponents;
        }

        @Override
        public void onFocus(FocusEvent event) {
            showTooltip();
        }

        @Override
        public void onMouseOver(MouseOverEvent event) {
            showTooltip();
        }

        @Override
        public void onBlur(BlurEvent event) {
            hideTooltip(false);
        }

        @Override
        public void onMouseOut(MouseOutEvent event) {
            hideTooltip(false);
        }

        @Override
        public void onClick(ClickEvent event) {
            // User may be clicking in invisible part of tool tip...
            // Force close tool tip.
            hideTooltip(true);
        }

        private final void hideTooltip(final boolean forceHide) {
            if (toolTipIdEquals(mToolTipId)
                    && (--mToolTipRefCount <= 0 || forceHide)) {
                assert (mTooltipHandlerEnabled);
                mToolTipCanvas.hide();
                mToolTipId = null;
                mToolTipRefCount = 0;
            }
        }

        private final boolean toolTipIdEquals(final String toolTipId) {
            final String thisToolTipId = getToolTipId();
            return (thisToolTipId != null) ? thisToolTipId.equals(toolTipId)
                    : false;
        }

        private final String getToolTipId() {
            return (mToolTipCanvas != null) ? mToolTipCanvas.getID() : null;
        }

        private final void showTooltip() {
            if (mTooltipHandlerEnabled) {
                if (toolTipIdEquals(mToolTipId)) {
                    ++mToolTipRefCount;
                } else if (mToolTipId != null) {
                } else {
                    // Compute tool tip orientation so tool tip is entirely
                    // visible
                    final Rectangle validationFieldPageRect = computeFieldPageRect();
                    if (validationFieldPageRect == null) {
                        // No fields to wrap
                        return;
                    }
                    assert (mContainingPage != null) : "content not added to a container?";
                    final Rectangle containerPageRect = mContainingPage
                            .getContainerPageRect();

                    if (mToolTipCanvas == null) {
                        mToolTipCanvas = makeBubbleTooltip();
                        orientAndPlaceTooltip(mToolTipCanvas,
                                validationFieldPageRect, containerPageRect);
                        mContainingPage.addChild(mToolTipCanvas);
                    } else {
                        orientAndPlaceTooltip(mToolTipCanvas,
                                validationFieldPageRect, containerPageRect);
                        mToolTipCanvas.show();
                    }
                    mToolTipId = getToolTipId();
                    mToolTipRefCount = 1;
                }
            }
        }

        private final ToolTipCanvas makeBubbleTooltip() {
            final IToolTipStyle toolTipStyle = getToolTipStyle(TooltipType.ERROR);
            final ToolTipCanvas toolTipCanvas = new ToolTipCanvas(toolTipStyle,
                    this);
            if (mTooltipMessageText != null) {
                toolTipCanvas.setToolTipMessage(mTooltipMessageText);
            }
            return toolTipCanvas;
        }

        private final void setToolTipMessage(final String msg) {
            if (mToolTipCanvas != null) {
                mToolTipCanvas.setToolTipMessage(msg);
            } else {
                // Tool tip not yet created: save text for when tool tip is
                // created
                mTooltipMessageText = msg;
            }
        }

        private final void clearToolTipMessage() {
            if (mToolTipCanvas != null) {
                mToolTipCanvas.clearToolTipMessage();
            } else {
                mTooltipMessageText = null;
            }
        }

        /**
         * Place the tool tip above or below the wrapped field
         */
        private final void orientAndPlaceTooltip(ToolTipCanvas toolTipCanvas,
                final Rectangle validationFieldPageRect,
                final Rectangle containerPageRect) {
            if (toolTipCanvas != null) {
                toolTipCanvas.orientAndPlaceTooltip(validationFieldPageRect,
                        containerPageRect, 0);
            }
        }

        /**
         * Compute the bounding rectangle of all fields.
         *
         * @return bounding rectangle of fields
         */
        private final Rectangle computeFieldPageRect() {
            return this.mContainingPage
                    .computeFieldPageRect(getValidationComponents());
        }

        private final List<IValidationComponent> identifyFormItems(
                final Canvas canvas) {
            final List<IValidationComponent> formItems = new ArrayList<IValidationComponent>();
            identifyFormItems(canvas, formItems);
            assert (formItems.size() > 0) : "no children form items: need to organize children first?";
            return formItems;
        }

        private final void identifyFormItems(final Canvas canvas,
                final List<IValidationComponent> formItems) {
            if (canvas instanceof IValidationComponent) {
                formItems.add((IValidationComponent) canvas);
            } else if (canvas instanceof Layout) {
                final Canvas[] members = ((Layout) canvas).getMembers();
                for (final Canvas child : members) {
                    identifyFormItems(child, formItems);
                }
            } else if (canvas instanceof DynamicForm) {
                final FormItem[] members = ((DynamicForm) canvas).getFields();
                for (final FormItem child : members) {
                    if (child instanceof IValidationComponent) {
                        formItems.add((IValidationComponent) child);
                    } else {
                        formItems.add(wrapAsIValidationComponent(child));
                    }
                }
            }
        }

        private final IValidationComponent wrapAsIValidationComponent(
                final FormItem child) {
            final IValidationComponent validationComponent = new IValidationComponent() {

                @Override
                public HandlerRegistration addBlurHandler(
                        BlurHandler blurHandler) {
                    // focus/blur not supported
                    return null;
                }

                @Override
                public HandlerRegistration addFocusHandler(
                        FocusHandler focusHandler) {
                    // focus/blur not supported
                    return null;
                }

                @Override
                public void clearValidationError() {
                    // not supported
                }

                @Override
                public void setValidationError() {
                    // not supported
                }

                @Override
                public void setValidationError(boolean indicateValidationError) {
                    if (indicateValidationError) {
                        setValidationError();
                    } else {
                        clearValidationError();
                    }
                }

                @Override
                public boolean doSupportValidation() {
                    return false;
                }

                @Override
                public Rectangle getFormPageRect() {
                    return child.getForm().getPageRect();
                }

            };
            return validationComponent;
        }

        @Override
        public void clearValidationError() {
            if (mTooltipHandlerEnabled) {
                hideTooltip(true);
            }
            mTooltipHandlerEnabled = false;
            clearToolTipMessage();
            applySetClearOp(clearValidationOp);
            // mContainingPage.setValidationFieldHasFocus(false, this);
        }

        @Override
        public void setValidationError(String validationMessage) {
            setToolTipMessage(validationMessage);
            mTooltipHandlerEnabled = true;
            applySetClearOp(setValidationOp);
        }

        private final void applySetClearOp(final IValidationVisualizationOp op) {
            boolean anyFieldShowsValidationError = false;
            boolean allFieldsShowValidationError = true;
            for (final IValidationComponent validationComponent : getValidationComponents()) {
                if (validationComponent.doSupportValidation()) {
                    op.instanceOp(validationComponent);
                    anyFieldShowsValidationError = true;
                } else {
                    allFieldsShowValidationError = false;
                }
            }
            if (!anyFieldShowsValidationError || !allFieldsShowValidationError) {
                op.globalOp(this);
            }
        }

        @Override
        public void setValidationError(boolean showValidationError,
                String validationMessage) {
            if (showValidationError) {
                setValidationError(validationMessage);
            }
        }

        private interface IValidationVisualizationOp {
            void instanceOp(final IValidationComponent validationComponent);

            void globalOp(final ValidationWrapper wrapper);
        };

        private final static IValidationVisualizationOp clearValidationOp = new IValidationVisualizationOp() {
            @Override
            public void instanceOp(IValidationComponent validationComponent) {
                validationComponent.clearValidationError();
            }

            @Override
            public void globalOp(final ValidationWrapper wrapper) {
                wrapper.setBorder(VALIDATION_ERROR_BORDER_WIDTH
                        + "px transparent");
                wrapper.markForRedraw();
            }
        };

        private final static IValidationVisualizationOp setValidationOp = new IValidationVisualizationOp() {
            @Override
            public void instanceOp(IValidationComponent validationComponent) {
                validationComponent.setValidationError();
            }

            @Override
            public void globalOp(final ValidationWrapper wrapper) {
                wrapper.setBorder(VALIDATION_ERROR_BORDER_WIDTH
                        + "px solid red");
                wrapper.markForRedraw();
            }
        };

    }

    public interface ITooltipPresenter {
        void show(final Rectangle containerPageRect);

        void hide();
    }

    public static ITooltipPresenter getTooltipPresenter(String toolTipText) {
        return getTooltipPresenter(TooltipType.INFO, toolTipText);
    }

    public static ITooltipPresenter getTooltipPresenter(TooltipType type,
            String toolTipText) {
        final TooltipPresenter presenter = new TooltipPresenter(type);
        presenter.setToolTipMessage(toolTipText);
        return presenter;
    }

    private static class TooltipPresenter implements ITooltipPresenter {

        private ToolTipCanvas mToolTipPresenterCanvas = null;
        private String mTooltipMessageText = null;

        // mToolTipId -- non-null means a tooltip was or is displayed
        private static String mToolTipId = null;

        // mToolTipRefCount -- > zero means a tool tip is displayed
        private int mToolTipRefCount = 0;

        private final TooltipType mType;

        TooltipPresenter(TooltipType type) {
            mType = type;
        }

        @Override
        public void hide() {
            if (toolTipIdEquals(mToolTipId) && (--mToolTipRefCount <= 0)) {
                mToolTipPresenterCanvas.hide();
                mToolTipId = null;
                mToolTipRefCount = 0;
            }
        }

        private final boolean toolTipIdEquals(final String toolTipId) {
            final String thisToolTipId = getToolTipId();
            return (thisToolTipId != null) ? thisToolTipId.equals(toolTipId)
                    : false;
        }

        private final String getToolTipId() {
            return (mToolTipPresenterCanvas != null) ? mToolTipPresenterCanvas
                    .getID() : null;
        }

        @Override
        public void show(final Rectangle imagePageRect) {
            if (toolTipIdEquals(mToolTipId)) {
                assert (mToolTipRefCount > 0);
                ++mToolTipRefCount;
            } else if (mToolTipId != null) {
            } else {
                // Compute tool tip orientation so tool tip is entirely
                // visible
                final Rectangle containerPageRect = new Rectangle(0, 0,
                        Page.getWidth(), Page.getHeight()); // ?
                                                            // computeFieldPageRect();
                if (imagePageRect == null) {
                    // No fields to wrap
                    return;
                }
                if (mToolTipPresenterCanvas == null) {
                    mToolTipPresenterCanvas = makeBubbleTooltip(mType);
                    orientAndPlaceTooltip(mToolTipPresenterCanvas,
                            imagePageRect, containerPageRect);
                } else {
                    orientAndPlaceTooltip(mToolTipPresenterCanvas,
                            imagePageRect, containerPageRect);
                }
                mToolTipPresenterCanvas.show();
                mToolTipPresenterCanvas.bringToFront();
                mToolTipId = getToolTipId();
                mToolTipRefCount = 1;
            }
        }

        public final ToolTipCanvas makeBubbleTooltip(TooltipType type) {
            final ClickHandler clickHandler = null;
            final IToolTipStyle toolTipStyle = getToolTipStyle(type);
            final ToolTipCanvas toolTipCanvas = new ToolTipCanvas(toolTipStyle,
                    clickHandler);
            if (mTooltipMessageText != null) {
                toolTipCanvas.setToolTipMessage(mTooltipMessageText);
            }
            return toolTipCanvas;
        }

        private final void setToolTipMessage(final String msg) {
            if (mToolTipPresenterCanvas != null) {
                mToolTipPresenterCanvas.setToolTipMessage(msg);
            } else {
                // Tool tip not yet created: save text for when tool tip is
                // created
                mTooltipMessageText = msg;
            }
        }

        private final void orientAndPlaceTooltip(ToolTipCanvas toolTipCanvas,
                final Rectangle validationFieldPageRect,
                final Rectangle containerPageRect) {
            if (toolTipCanvas != null) {
                toolTipCanvas.orientAndPlaceTooltip(validationFieldPageRect,
                        containerPageRect, validationFieldPageRect.getWidth());
            }
        }
    }

    private interface IToolTipStyle {

        String getBackgroundColor();

        Boolean getShowEdges();

        String getEdgeImage();

        int getEdgeSize();
    }

    private static class ToolTipCanvas extends VLayout {

        private enum TooltipBubbleOrientation {
            SHOW_ABOVE, SHOW_BELOW, SHOW_ABOVE_LEFT, SHOW_BELOW_LEFT, INVALID
        }

        private static final int TOOLTIP_TRANSPARENT_CONTAINER_HEIGHT = 200;
        private static final int TOOLTIP_TRANSPARENT_CONTAINER_WIDTH = 300;
        private static final int TOOLTIP_BALLOON_ARROW_WIDTH = 88;
        private static final int TOOLTIP_BALLOON_XOFFSET = 23;

        private final LayoutSpacer mSpacer;
        private final Label mInnerLabel;
        private final HLayout mArrowLayout;
        private final Label mArrowLabel;
        private final LayoutSpacer mArrowSpacer;

        private final IToolTipStyle mToolTipStyle;
        private String mToolTipMessage = null;

        ToolTipCanvas(IToolTipStyle toolTipStyle,
                final ClickHandler clickHandler) {
            mToolTipStyle = toolTipStyle;
            mSpacer = new LayoutSpacer();
            this.setMemberOverlap(mToolTipStyle.getEdgeSize());
            this.setHeight(TOOLTIP_TRANSPARENT_CONTAINER_HEIGHT);
            if (clickHandler != null) {
                this.addClickHandler(clickHandler);
            }

            mInnerLabel = new Label();
            mInnerLabel.setPadding(10);
            mInnerLabel.setMaxWidth(250);

            if (mToolTipMessage != null) {
                mInnerLabel.setContents(mToolTipMessage);
            }
            shrinkWrap(mInnerLabel);
            mInnerLabel.setWidth(TOOLTIP_TRANSPARENT_CONTAINER_WIDTH);
            // Define tool tip balloon's border
            mInnerLabel.setShowEdges(mToolTipStyle.getShowEdges());
            mInnerLabel.setEdgeImage(mToolTipStyle.getEdgeImage());
            mInnerLabel.setEdgeSize(mToolTipStyle.getEdgeSize());
            mInnerLabel.setEdgeCenterBackgroundColor(mToolTipStyle
                    .getBackgroundColor());

            mArrowLayout = new HLayout();
            shrinkWrap(mArrowLayout);
            mArrowLayout.setWidth100();

            mArrowSpacer = new LayoutSpacer();
            mArrowSpacer.setWidth(TOOLTIP_TRANSPARENT_CONTAINER_WIDTH
                    - TOOLTIP_BALLOON_ARROW_WIDTH);

            mArrowLabel = new Label();
            shrinkWrap(mArrowLabel);
            mArrowLabel.setAlign(Alignment.CENTER);
            mArrowLabel.moveAbove(mInnerLabel);
            mArrowLabel.setWidth(TOOLTIP_BALLOON_ARROW_WIDTH);
        }

        private static TooltipBubbleOrientation computeTooltipOrientation(
                final Rectangle validationFieldPageRect,
                final Rectangle containerPageRect) {
            assert (validationFieldPageRect != null) : "invalid null argument";
            final int xFieldRight = validationFieldPageRect.getLeft()
                    + validationFieldPageRect.getWidth();
            final boolean flipTooltip = (xFieldRight + (TOOLTIP_TRANSPARENT_CONTAINER_WIDTH >> 1)) > containerPageRect
                    .getWidth();
            final int yFieldBottom = validationFieldPageRect.getTop()
                    + validationFieldPageRect.getHeight();
            final TooltipBubbleOrientation tooltipOrientation;
            if ((yFieldBottom - Page.getScrollTop())
                    + TOOLTIP_TRANSPARENT_CONTAINER_HEIGHT >= containerPageRect
                        .getHeight()) {
                tooltipOrientation = (flipTooltip) ? TooltipBubbleOrientation.SHOW_ABOVE_LEFT
                        : TooltipBubbleOrientation.SHOW_ABOVE;
            } else {
                tooltipOrientation = (flipTooltip) ? TooltipBubbleOrientation.SHOW_BELOW_LEFT
                        : TooltipBubbleOrientation.SHOW_BELOW;
            }
            return tooltipOrientation;
        }

        private void orientAndPlaceTooltip(Rectangle validationFieldPageRect,
                final Rectangle containerPageRect, int horizontalOffset) {
            final TooltipBubbleOrientation orientation = computeTooltipOrientation(
                    validationFieldPageRect, containerPageRect);
            if (orientation == TooltipBubbleOrientation.SHOW_ABOVE_LEFT
                    || orientation == TooltipBubbleOrientation.SHOW_BELOW_LEFT) {
                mArrowLayout.setMembers(mArrowSpacer, mArrowLabel);
            } else {
                mArrowLayout.setMembers(mArrowLabel, mArrowSpacer);
            }

            if (orientation == TooltipBubbleOrientation.SHOW_ABOVE
                    || orientation == TooltipBubbleOrientation.SHOW_ABOVE_LEFT) {
                this.setMembers(mSpacer, mInnerLabel, mArrowLayout);
            } else {
                assert (orientation == TooltipBubbleOrientation.SHOW_BELOW || orientation == TooltipBubbleOrientation.SHOW_BELOW_LEFT) : "invalid orientation";
                this.setMembers(mArrowLayout, mInnerLabel, mSpacer);
            }

            final int top;
            final int left;
            if (TooltipBubbleOrientation.SHOW_ABOVE.equals(orientation)
                    || orientation == TooltipBubbleOrientation.SHOW_ABOVE_LEFT) {
                top = validationFieldPageRect.getTop()
                        - TOOLTIP_TRANSPARENT_CONTAINER_HEIGHT;
            } else {
                assert (TooltipBubbleOrientation.SHOW_BELOW.equals(orientation) || orientation == TooltipBubbleOrientation.SHOW_BELOW_LEFT);
                top = validationFieldPageRect.getTop()
                        + validationFieldPageRect.getHeight();
            }
            if (orientation == TooltipBubbleOrientation.SHOW_ABOVE_LEFT
                    || orientation == TooltipBubbleOrientation.SHOW_BELOW_LEFT) {
                left = validationFieldPageRect.getLeft()
                        - TOOLTIP_TRANSPARENT_CONTAINER_WIDTH
                        + validationFieldPageRect.getWidth()
                        + TOOLTIP_BALLOON_XOFFSET;
            } else {
                left = validationFieldPageRect.getLeft() - horizontalOffset
                        - TOOLTIP_BALLOON_XOFFSET;
            }
            this.setTop(top);
            this.setLeft(left);
        }

        public void setToolTipMessage(String msg) {
            mToolTipMessage = (mToolTipMessage == null) ? msg : mToolTipMessage
                    + "; " + msg;
            if (mInnerLabel != null) {
                mInnerLabel.setContents(msg);
            }
        }

        public void clearToolTipMessage() {
            mToolTipMessage = null;
        }

    }

    /**
     * DynamicForm with basic test automation support
     */
    private static class DynamicFormForTestAutomation extends DynamicForm {
        DynamicFormForTestAutomation() {
            super();
            this.setBrowserSpellCheck(Boolean.FALSE);
        }

    }

    public interface IValidationComponent {
        HandlerRegistration addBlurHandler(
                com.smartgwt.client.widgets.form.fields.events.BlurHandler blurHandler);

        HandlerRegistration addFocusHandler(
                com.smartgwt.client.widgets.form.fields.events.FocusHandler focusHandler);

        void clearValidationError();

        void setValidationError();

        void setValidationError(boolean indicateValidationError);

        Rectangle getFormPageRect();

        boolean doSupportValidation();
    }

    public interface IValidationField {
        void clearValidationError();

        void setValidationError(String validationMessage);

        void setValidationError(boolean showValidationError,
                String validationMessage);
    }

    private static IToolTipStyle getToolTipStyle(TooltipType type) {
        switch (type) {
        case ERROR:
            return getErrorToolTipStyle();
        case WARNING:
            return getInfoToolTipStyle();
        default:
            return getInfoToolTipStyle();
        }
    }

    private static IToolTipStyle getErrorToolTipStyle() {
        final String TOOLTIP_BALLOON_BACKGROUND_COLOR = "#123123";
        final String TOOLTIP_EDGE_PNG = "edge_FF0000.png";
        final int TOOLTIP_BORDER_WIDTH = 1;

        return new IToolTipStyle() {
            @Override
            public String getBackgroundColor() {
                return TOOLTIP_BALLOON_BACKGROUND_COLOR;
            }

            @Override
            public Boolean getShowEdges() {
                return Boolean.TRUE;
            }

            @Override
            public String getEdgeImage() {
                return TOOLTIP_EDGE_PNG;
            }

            @Override
            public int getEdgeSize() {
                return TOOLTIP_BORDER_WIDTH;
            }

        };
    }

    private static IToolTipStyle getInfoToolTipStyle() {
        final String TOOLTIP_BALLOON_BACKGROUND_COLOR = "#FFFFFF";
        final String TOOLTIP_EDGE_PNG = "edge_7F7F7F.png";
        final int TOOLTIP_BORDER_WIDTH = 1;

        return new IToolTipStyle() {
            @Override
            public String getBackgroundColor() {
                return TOOLTIP_BALLOON_BACKGROUND_COLOR;
            }

            @Override
            public Boolean getShowEdges() {
                return Boolean.TRUE;
            }

            @Override
            public String getEdgeImage() {
                return TOOLTIP_EDGE_PNG;
            }

            @Override
            public int getEdgeSize() {
                return TOOLTIP_BORDER_WIDTH;
            }

        };
    }

    public static void shrinkWrap(final Canvas canvas) {
        canvas.setAutoHeight();
        canvas.setAutoWidth();
        canvas.setOverflow(Overflow.VISIBLE);
    }

    public enum TooltipType {
        INFO("info"), WARNING("warning"), ERROR("error");

        private String mValue;

        TooltipType(String value) {
            mValue = value;
        }

        public String getmValue() {
            return mValue;
        }

        public void setmValue(String mValue) {
            this.mValue = mValue;
        }

        public static TooltipType fromValue(String value) {
            for (TooltipType type : TooltipType.values()) {
                if (type.getmValue().equals(value)) {
                    return type;
                }
            }
            return INFO;
        }
    }
}
