/*
@ITMillApache2LicenseForJavaFiles@
 */
package com.vaadin.terminal.gwt.client.ui;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.Set;

import com.google.gwt.core.client.JsArrayString;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.DomEvent.Type;
import com.google.gwt.event.shared.EventHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.terminal.gwt.client.ApplicationConnection;
import com.vaadin.terminal.gwt.client.BrowserInfo;
import com.vaadin.terminal.gwt.client.Paintable;
import com.vaadin.terminal.gwt.client.RenderInformation.FloatSize;
import com.vaadin.terminal.gwt.client.RenderInformation.Size;
import com.vaadin.terminal.gwt.client.RenderSpace;
import com.vaadin.terminal.gwt.client.UIDL;
import com.vaadin.terminal.gwt.client.Util;
import com.vaadin.terminal.gwt.client.ValueMap;
import com.vaadin.terminal.gwt.client.ui.layout.CellBasedLayout;
import com.vaadin.terminal.gwt.client.ui.layout.ChildComponentContainer;

public class VOrderedLayout extends CellBasedLayout {

    public static final String CLASSNAME = "v-orderedlayout";

    public static final String CLICK_EVENT_IDENTIFIER = "click";

    private int orientation;

    // Can be removed once OrderedLayout is removed
    private boolean allowOrientationUpdate = false;

    /**
     * Size of the layout excluding any margins.
     */
    private Size activeLayoutSize = new Size(0, 0);

    private boolean isRendering = false;

    private String width = "";

    private boolean sizeHasChangedDuringRendering = false;

    private ValueMap expandRatios;

    private double expandRatioSum;

    private double defaultExpandRatio;

    private ValueMap alignments;

    private LayoutClickEventHandler clickEventHandler = new LayoutClickEventHandler(
            this, CLICK_EVENT_IDENTIFIER) {

        @Override
        protected Paintable getChildComponent(Element element) {
            return getComponent(element);
        }

        @Override
        protected <H extends EventHandler> HandlerRegistration registerHandler(
                H handler, Type<H> type) {
            return addDomHandler(handler, type);
        }
    };

    public VOrderedLayout() {
        this(CLASSNAME, ORIENTATION_VERTICAL);
        allowOrientationUpdate = true;
    }

    protected VOrderedLayout(String className, int orientation) {
        setStyleName(className);
        this.orientation = orientation;

        STYLENAME_SPACING = className + "-spacing";
        STYLENAME_MARGIN_TOP = className + "-margin-top";
        STYLENAME_MARGIN_RIGHT = className + "-margin-right";
        STYLENAME_MARGIN_BOTTOM = className + "-margin-bottom";
        STYLENAME_MARGIN_LEFT = className + "-margin-left";
    }

    @Override
    public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
        isRendering = true;
        super.updateFromUIDL(uidl, client);

        // Only non-cached, visible UIDL:s can introduce changes
        if (uidl.getBooleanAttribute("cached")
                || uidl.getBooleanAttribute("invisible")) {
            isRendering = false;
            return;
        }

        clickEventHandler.handleEventHandlerRegistration(client);

        if (allowOrientationUpdate) {
            handleOrientationUpdate(uidl);
        }

        // IStopWatch w = new IStopWatch("OrderedLayout.updateFromUIDL");

        ArrayList<Widget> uidlWidgets = new ArrayList<Widget>(
                uidl.getChildCount());
        ArrayList<ChildComponentContainer> relativeSizeComponents = new ArrayList<ChildComponentContainer>();
        ArrayList<UIDL> relativeSizeComponentUIDL = new ArrayList<UIDL>();

        int pos = 0;
        for (final Iterator<Object> it = uidl.getChildIterator(); it.hasNext();) {
            final UIDL childUIDL = (UIDL) it.next();
            final Paintable child = client.getPaintable(childUIDL);
            Widget widget = (Widget) child;

            // Create container for component
            ChildComponentContainer childComponentContainer = getComponentContainer(widget);

            if (childComponentContainer == null) {
                // This is a new component
                childComponentContainer = createChildContainer(widget);
            }

            addOrMoveChild(childComponentContainer, pos++);

            /*
             * Components which are to be expanded in the same orientation as
             * the layout are rendered later when it is clear how much space
             * they can use
             */
            if (!Util.isCached(childUIDL)) {
                FloatSize relativeSize = Util.parseRelativeSize(childUIDL);
                childComponentContainer.setRelativeSize(relativeSize);
            }

            if (childComponentContainer.isComponentRelativeSized(orientation)) {
                relativeSizeComponents.add(childComponentContainer);
                relativeSizeComponentUIDL.add(childUIDL);
            } else {
                if (isDynamicWidth()) {
                    childComponentContainer.renderChild(childUIDL, client, -1);
                } else {
                    childComponentContainer.renderChild(childUIDL, client,
                            activeLayoutSize.getWidth());
                }
                if (sizeHasChangedDuringRendering && Util.isCached(childUIDL)) {
                    // notify cached relative sized component about size
                    // chance
                    client.handleComponentRelativeSize(childComponentContainer
                            .getWidget());
                }
            }

            uidlWidgets.add(widget);

        }

        // w.mark("Rendering of "
        // + (uidlWidgets.size() - relativeSizeComponents.size())
        // + " absolute size components done");

        /*
         * Remove any children after pos. These are the ones that previously
         * were in the layout but have now been removed
         */
        removeChildrenAfter(pos);

        // w.mark("Old children removed");

        /* Fetch alignments and expand ratio from UIDL */
        updateAlignmentsAndExpandRatios(uidl, uidlWidgets);
        // w.mark("Alignments and expand ratios updated");

        /* Fetch widget sizes from rendered components */
        updateWidgetSizes();
        // w.mark("Widget sizes updated");

        recalculateLayout();
        // w.mark("Layout size calculated (" + activeLayoutSize +
        // ") offsetSize: "
        // + getOffsetWidth() + "," + getOffsetHeight());

        /* Render relative size components */
        for (int i = 0; i < relativeSizeComponents.size(); i++) {
            ChildComponentContainer childComponentContainer = relativeSizeComponents
                    .get(i);
            UIDL childUIDL = relativeSizeComponentUIDL.get(i);

            if (isDynamicWidth()) {
                childComponentContainer.renderChild(childUIDL, client, -1);
            } else {
                childComponentContainer.renderChild(childUIDL, client,
                        activeLayoutSize.getWidth());
            }

            if (Util.isCached(childUIDL)) {
                /*
                 * We must update the size of the relative sized component if
                 * the expand ratio or something else in the layout changes
                 * which affects the size of a relative sized component
                 */
                client.handleComponentRelativeSize(childComponentContainer
                        .getWidget());
            }

            // childComponentContainer.updateWidgetSize();
        }

        // w.mark("Rendering of " + (relativeSizeComponents.size())
        // + " relative size components done");

        /* Fetch widget sizes for relative size components */
        for (ChildComponentContainer childComponentContainer : widgetToComponentContainer
                .values()) {

            /* Update widget size from DOM */
            childComponentContainer.updateWidgetSize();
        }

        // w.mark("Widget sizes updated");

        /*
         * Components with relative size in main direction may affect the layout
         * size in the other direction
         */
        if ((isHorizontal() && isDynamicHeight())
                || (isVertical() && isDynamicWidth())) {
            layoutSizeMightHaveChanged();
        }
        // w.mark("Layout dimensions updated");

        /* Update component spacing */
        updateContainerMargins();

        /*
         * Update component sizes for components with relative size in non-main
         * direction
         */
        if (updateRelativeSizesInNonMainDirection()) {
            // Sizes updated - might affect the other dimension so we need to
            // recheck the widget sizes and recalculate layout dimensions
            updateWidgetSizes();
            layoutSizeMightHaveChanged();
        }
        calculateAlignments();
        // w.mark("recalculateComponentSizesAndAlignments done");

        setRootSize();

        if (BrowserInfo.get().isIE()) {
            /*
             * This should fix the issue with padding not always taken into
             * account for the containers leading to no spacing between
             * elements.
             */
            root.getStyle().setProperty("zoom", "1");
        }

        // w.mark("runDescendentsLayout done");
        isRendering = false;
        sizeHasChangedDuringRendering = false;
    }

    private void layoutSizeMightHaveChanged() {
        Size oldSize = new Size(activeLayoutSize.getWidth(),
                activeLayoutSize.getHeight());
        calculateLayoutDimensions();

        /*
         * If layout dimension changes we must also update container sizes
         */
        if (!oldSize.equals(activeLayoutSize)) {
            calculateContainerSize();
        }
    }

    private void updateWidgetSizes() {
        for (ChildComponentContainer childComponentContainer : widgetToComponentContainer
                .values()) {

            /*
             * Update widget size from DOM
             */
            childComponentContainer.updateWidgetSize();
        }
    }

    private void recalculateLayout() {

        /* Calculate space for relative size components */
        int spaceForExpansion = calculateLayoutDimensions();

        if (!widgetToComponentContainer.isEmpty()) {
            /* Divide expansion space between component containers */
            expandComponentContainers(spaceForExpansion);

            /* Update container sizes */
            calculateContainerSize();
        }

    }

    private void expandComponentContainers(int spaceForExpansion) {
        int remaining = spaceForExpansion;
        for (ChildComponentContainer childComponentContainer : widgetToComponentContainer
                .values()) {
            remaining -= childComponentContainer.expand(orientation,
                    spaceForExpansion);
        }

        if (remaining > 0) {

            // Some left-over pixels due to rounding errors

            // Add one pixel to each container until there are no pixels left
            // FIXME extra pixels should be divided among expanded widgets if
            // such a widgets exists

            Iterator<Widget> widgetIterator = iterator();
            while (widgetIterator.hasNext() && remaining-- > 0) {
                ChildComponentContainer childComponentContainer = (ChildComponentContainer) widgetIterator
                        .next();
                childComponentContainer.expandExtra(orientation, 1);
            }
        }

    }

    private void handleOrientationUpdate(UIDL uidl) {
        int newOrientation = ORIENTATION_VERTICAL;
        if ("horizontal".equals(uidl.getStringAttribute("orientation"))) {
            newOrientation = ORIENTATION_HORIZONTAL;
        }

        if (orientation != newOrientation) {
            orientation = newOrientation;

            for (ChildComponentContainer childComponentContainer : widgetToComponentContainer
                    .values()) {
                childComponentContainer.setOrientation(orientation);
            }
        }

    }

    /**
     * Updated components with relative height in horizontal layouts and
     * components with relative width in vertical layouts. This is only needed
     * if the height (horizontal layout) or width (vertical layout) has not been
     * specified.
     */
    private boolean updateRelativeSizesInNonMainDirection() {
        int updateDirection = 1 - orientation;
        if ((updateDirection == ORIENTATION_HORIZONTAL && !isDynamicWidth())
                || (updateDirection == ORIENTATION_VERTICAL && !isDynamicHeight())) {
            return false;
        }

        boolean updated = false;
        for (ChildComponentContainer componentContainer : widgetToComponentContainer
                .values()) {
            if (componentContainer.isComponentRelativeSized(updateDirection)) {
                client.handleComponentRelativeSize(componentContainer
                        .getWidget());
            }

            updated = true;
        }

        return updated;
    }

    private int calculateLayoutDimensions() {
        int summedWidgetWidth = 0;
        int summedWidgetHeight = 0;

        int maxWidgetWidth = 0;
        int maxWidgetHeight = 0;

        // Calculate layout dimensions from component dimensions
        for (ChildComponentContainer childComponentContainer : widgetToComponentContainer
                .values()) {

            int widgetHeight = 0;
            int widgetWidth = 0;
            if (childComponentContainer.isComponentRelativeSized(orientation)) {
                if (orientation == ORIENTATION_HORIZONTAL) {
                    widgetHeight = getWidgetHeight(childComponentContainer);
                } else {
                    widgetWidth = getWidgetWidth(childComponentContainer);
                }
            } else {
                widgetWidth = getWidgetWidth(childComponentContainer);
                widgetHeight = getWidgetHeight(childComponentContainer);
            }

            summedWidgetWidth += widgetWidth;
            summedWidgetHeight += widgetHeight;

            maxWidgetHeight = Math.max(maxWidgetHeight, widgetHeight);
            maxWidgetWidth = Math.max(maxWidgetWidth, widgetWidth);
        }

        if (isHorizontal()) {
            summedWidgetWidth += activeSpacing.hSpacing
                    * (widgetToComponentContainer.size() - 1);
        } else {
            summedWidgetHeight += activeSpacing.vSpacing
                    * (widgetToComponentContainer.size() - 1);
        }

        Size layoutSize = updateLayoutDimensions(summedWidgetWidth,
                summedWidgetHeight, maxWidgetWidth, maxWidgetHeight);

        int remainingSpace;
        if (isHorizontal()) {
            remainingSpace = layoutSize.getWidth() - summedWidgetWidth;
        } else {
            remainingSpace = layoutSize.getHeight() - summedWidgetHeight;
        }
        if (remainingSpace < 0) {
            remainingSpace = 0;
        }

        // ApplicationConnection.getConsole().log(
        // "Layout size: " + activeLayoutSize);
        return remainingSpace;
    }

    private int getWidgetHeight(ChildComponentContainer childComponentContainer) {
        Size s = childComponentContainer.getWidgetSize();
        return s.getHeight()
                + childComponentContainer.getCaptionHeightAboveComponent();
    }

    private int getWidgetWidth(ChildComponentContainer childComponentContainer) {
        Size s = childComponentContainer.getWidgetSize();
        int widgetWidth = s.getWidth()
                + childComponentContainer.getCaptionWidthAfterComponent();

        /*
         * If the component does not have a specified size in the main direction
         * the caption may determine the space used by the component
         */
        if (!childComponentContainer.widgetHasSizeSpecified(orientation)) {
            int captionWidth = childComponentContainer
                    .getCaptionRequiredWidth();

            if (captionWidth > widgetWidth) {
                widgetWidth = captionWidth;
            }
        }

        return widgetWidth;
    }

    private void calculateAlignments() {
        int w = 0;
        int h = 0;

        if (isHorizontal()) {
            // HORIZONTAL
            h = activeLayoutSize.getHeight();
            if (!isDynamicWidth()) {
                w = -1;
            }

        } else {
            // VERTICAL
            w = activeLayoutSize.getWidth();
            if (!isDynamicHeight()) {
                h = -1;
            }
        }

        for (ChildComponentContainer childComponentContainer : widgetToComponentContainer
                .values()) {
            childComponentContainer.updateAlignments(w, h);
        }

    }

    private void calculateContainerSize() {

        /*
         * Container size here means the size the container gets from the
         * component. The expansion size is not include in this but taken
         * separately into account.
         */
        int height = 0, width = 0;
        Iterator<Widget> widgetIterator = iterator();
        if (isHorizontal()) {
            height = activeLayoutSize.getHeight();
            int availableWidth = activeLayoutSize.getWidth();
            boolean first = true;
            while (widgetIterator.hasNext()) {
                ChildComponentContainer childComponentContainer = (ChildComponentContainer) widgetIterator
                        .next();
                if (!childComponentContainer
                        .isComponentRelativeSized(ORIENTATION_HORIZONTAL)) {
                    /*
                     * Only components with non-relative size in the main
                     * direction has a container size
                     */
                    width = childComponentContainer.getWidgetSize().getWidth()
                            + childComponentContainer
                                    .getCaptionWidthAfterComponent();

                    /*
                     * If the component does not have a specified size in the
                     * main direction the caption may determine the space used
                     * by the component
                     */
                    if (!childComponentContainer
                            .widgetHasSizeSpecified(orientation)) {
                        int captionWidth = childComponentContainer
                                .getCaptionRequiredWidth();
                        // ApplicationConnection.getConsole().log(
                        // "Component width: " + width
                        // + ", caption width: " + captionWidth);
                        if (captionWidth > width) {
                            width = captionWidth;
                        }
                    }
                } else {
                    width = 0;
                }

                if (!isDynamicWidth()) {
                    if (availableWidth == 0) {
                        /*
                         * Let the overflowing components overflow. IE has
                         * problems with zero sizes.
                         */
                        // width = 0;
                        // height = 0;
                    } else if (width > availableWidth) {
                        width = availableWidth;

                        if (!first) {
                            width -= activeSpacing.hSpacing;
                        }
                        availableWidth = 0;
                    } else {
                        availableWidth -= width;
                        if (!first) {
                            availableWidth -= activeSpacing.hSpacing;
                        }
                    }

                    first = false;
                }

                childComponentContainer.setContainerSize(width, height);
            }
        } else {
            width = activeLayoutSize.getWidth();
            while (widgetIterator.hasNext()) {
                ChildComponentContainer childComponentContainer = (ChildComponentContainer) widgetIterator
                        .next();

                if (!childComponentContainer
                        .isComponentRelativeSized(ORIENTATION_VERTICAL)) {
                    /*
                     * Only components with non-relative size in the main
                     * direction has a container size
                     */
                    height = childComponentContainer.getWidgetSize()
                            .getHeight()
                            + childComponentContainer
                                    .getCaptionHeightAboveComponent();
                } else {
                    height = 0;
                }

                childComponentContainer.setContainerSize(width, height);
            }

        }

    }

    private Size updateLayoutDimensions(int totalComponentWidth,
            int totalComponentHeight, int maxComponentWidth,
            int maxComponentHeight) {

        /* Only need to calculate dynamic dimensions */
        if (!isDynamicHeight() && !isDynamicWidth()) {
            return activeLayoutSize;
        }

        int activeLayoutWidth = 0;
        int activeLayoutHeight = 0;

        // Update layout dimensions
        if (isHorizontal()) {
            // Horizontal
            if (isDynamicWidth()) {
                activeLayoutWidth = totalComponentWidth;
            }

            if (isDynamicHeight()) {
                activeLayoutHeight = maxComponentHeight;
            }

        } else {
            // Vertical
            if (isDynamicWidth()) {
                activeLayoutWidth = maxComponentWidth;
            }

            if (isDynamicHeight()) {
                activeLayoutHeight = totalComponentHeight;
            }
        }

        if (isDynamicWidth()) {
            setActiveLayoutWidth(activeLayoutWidth);
            setOuterLayoutWidth(activeLayoutSize.getWidth());
        }

        if (isDynamicHeight()) {
            setActiveLayoutHeight(activeLayoutHeight);
            setOuterLayoutHeight(activeLayoutSize.getHeight());
        }

        return activeLayoutSize;
    }

    private void setActiveLayoutWidth(int activeLayoutWidth) {
        if (activeLayoutWidth < 0) {
            activeLayoutWidth = 0;
        }
        activeLayoutSize.setWidth(activeLayoutWidth);
    }

    private void setActiveLayoutHeight(int activeLayoutHeight) {
        if (activeLayoutHeight < 0) {
            activeLayoutHeight = 0;
        }
        activeLayoutSize.setHeight(activeLayoutHeight);

    }

    private void setOuterLayoutWidth(int activeLayoutWidth) {
        // Don't call setWidth to avoid triggering all kinds of recalculations
        // Also don't call super.setWidth to avoid messing with the
        // dynamicWidth property
        int newPixelWidth = (activeLayoutWidth + activeMargins.getHorizontal());
        getElement().getStyle().setWidth(newPixelWidth, Unit.PX);
    }

    private void setOuterLayoutHeight(int activeLayoutHeight) {
        // Don't call setHeight to avoid triggering all kinds of recalculations
        // Also don't call super.setHeight to avoid messing with the
        // dynamicHeight property
        int newPixelHeight = (activeLayoutHeight + activeMargins.getVertical());
        getElement().getStyle().setHeight(newPixelHeight, Unit.PX);
    }

    /**
     * Updates the spacing between components. Needs to be done only when
     * components are added/removed.
     */
    private void updateContainerMargins() {
        ChildComponentContainer firstChildComponent = getFirstChildComponentContainer();
        if (firstChildComponent != null) {
            firstChildComponent.setMarginLeft(0);
            firstChildComponent.setMarginTop(0);

            for (ChildComponentContainer childComponent : widgetToComponentContainer
                    .values()) {
                if (childComponent == firstChildComponent) {
                    continue;
                }

                if (isHorizontal()) {
                    childComponent.setMarginLeft(activeSpacing.hSpacing);
                } else {
                    childComponent.setMarginTop(activeSpacing.vSpacing);
                }
            }
        }
    }

    private boolean isHorizontal() {
        return orientation == ORIENTATION_HORIZONTAL;
    }

    private boolean isVertical() {
        return orientation == ORIENTATION_VERTICAL;
    }

    private ChildComponentContainer createChildContainer(Widget child) {

        // Create a container DIV for the child
        ChildComponentContainer childComponent = new ChildComponentContainer(
                child, orientation);

        return childComponent;

    }

    public RenderSpace getAllocatedSpace(Widget child) {
        int width = 0;
        int height = 0;
        ChildComponentContainer childComponentContainer = getComponentContainer(child);
        // WIDTH CALCULATION
        if (isVertical()) {
            width = activeLayoutSize.getWidth();
            width -= childComponentContainer.getCaptionWidthAfterComponent();
        } else if (!isDynamicWidth()) {
            // HORIZONTAL
            width = childComponentContainer.getContSize().getWidth();
            width -= childComponentContainer.getCaptionWidthAfterComponent();
        }

        // HEIGHT CALCULATION
        if (isHorizontal()) {
            height = activeLayoutSize.getHeight();
            height -= childComponentContainer.getCaptionHeightAboveComponent();
        } else if (!isDynamicHeight()) {
            // VERTICAL
            height = childComponentContainer.getContSize().getHeight();
            height -= childComponentContainer.getCaptionHeightAboveComponent();
        }

        // ApplicationConnection.getConsole().log(
        // "allocatedSpace for " + Util.getSimpleName(child) + ": "
        // + width + "," + height);
        RenderSpace space = new RenderSpace(width, height);
        return space;
    }

    private void recalculateLayoutAndComponentSizes() {
        recalculateLayout();

        if (!(isDynamicHeight() && isDynamicWidth())) {
            /* First update relative sized components */
            for (ChildComponentContainer componentContainer : widgetToComponentContainer
                    .values()) {
                client.handleComponentRelativeSize(componentContainer
                        .getWidget());

                // Update widget size from DOM
                componentContainer.updateWidgetSize();
            }
        }

        if (isDynamicHeight()) {
            /*
             * Height is not necessarily correct anymore as the height of
             * components might have changed if the width has changed.
             */

            /*
             * Get the new widget sizes from DOM and calculate new container
             * sizes
             */
            updateWidgetSizes();

            /* Update layout dimensions based on widget sizes */
            recalculateLayout();
        }

        updateRelativeSizesInNonMainDirection();
        calculateAlignments();

        setRootSize();
    }

    private void setRootSize() {
        root.getStyle().setPropertyPx("width", activeLayoutSize.getWidth());
        root.getStyle().setPropertyPx("height", activeLayoutSize.getHeight());
    }

    public boolean requestLayout(Set<Paintable> children) {
        for (Paintable p : children) {
            /* Update widget size from DOM */
            ChildComponentContainer componentContainer = getComponentContainer((Widget) p);
            // This should no longer be needed (after #2563)
            // if (isDynamicWidth()) {
            // componentContainer.setUnlimitedContainerWidth();
            // } else {
            // componentContainer.setLimitedContainerWidth(activeLayoutSize
            // .getWidth());
            // }

            componentContainer.updateWidgetSize();

            /*
             * If this is the result of an caption icon onload event the caption
             * size may have changed
             */
            componentContainer.updateCaptionSize();
        }

        Size sizeBefore = new Size(activeLayoutSize.getWidth(),
                activeLayoutSize.getHeight());

        recalculateLayoutAndComponentSizes();
        boolean sameSize = (sizeBefore.equals(activeLayoutSize));
        if (!sameSize) {
            /* Must inform child components about possible size updates */
            client.runDescendentsLayout(this);
        }

        /* Automatically propagated upwards if the size has changed */

        return sameSize;
    }

    @Override
    public void setHeight(String height) {
        Size sizeBefore = new Size(activeLayoutSize.getWidth(),
                activeLayoutSize.getHeight());

        super.setHeight(height);

        if (height != null && !height.equals("")) {
            setActiveLayoutHeight(getOffsetHeight()
                    - activeMargins.getVertical());
        }

        if (isRendering) {
            sizeHasChangedDuringRendering = true;
        } else {
            recalculateLayoutAndComponentSizes();
            boolean sameSize = (sizeBefore.equals(activeLayoutSize));
            if (!sameSize) {
                /* Must inform child components about possible size updates */
                client.runDescendentsLayout(this);
            }
        }
    }

    @Override
    public void setWidth(String width) {
        if (this.width.equals(width) || !isVisible()) {
            return;
        }
        Size sizeBefore = new Size(activeLayoutSize.getWidth(),
                activeLayoutSize.getHeight());

        super.setWidth(width);
        this.width = width;
        if (width != null && !width.equals("")) {
            setActiveLayoutWidth(getOffsetWidth()
                    - activeMargins.getHorizontal());
        }

        if (isRendering) {
            sizeHasChangedDuringRendering = true;
        } else {
            recalculateLayoutAndComponentSizes();
            boolean sameSize = (sizeBefore.equals(activeLayoutSize));
            if (!sameSize) {
                /* Must inform child components about possible size updates */
                client.runDescendentsLayout(this);
            }
            /*
             * If the height changes as a consequence of this we must inform the
             * parent also
             */
            if (isDynamicHeight()
                    && sizeBefore.getHeight() != activeLayoutSize.getHeight()) {
                Util.notifyParentOfSizeChange(this, false);
            }

        }
    }

    protected void updateAlignmentsAndExpandRatios(UIDL uidl,
            ArrayList<Widget> renderedWidgets) {

        /*
         */
        alignments = uidl.getMapAttribute("alignments");

        /*
         * UIDL contains a map of paintable ids to expand ratios
         */

        expandRatios = uidl.getMapAttribute("expandRatios");
        expandRatioSum = -1.0;

        for (int i = 0; i < renderedWidgets.size(); i++) {
            Widget widget = renderedWidgets.get(i);
            String pid = client.getPid(widget.getElement());

            ChildComponentContainer container = getComponentContainer(widget);

            // Calculate alignment info
            container.setAlignment(getAlignment(pid));

            // Update expand ratio
            container.setNormalizedExpandRatio(getExpandRatio(pid));
        }
    }

    private AlignmentInfo getAlignment(String pid) {
        if (alignments.containsKey(pid)) {
            return new AlignmentInfo(alignments.getInt(pid));
        } else {
            return AlignmentInfo.TOP_LEFT;
        }
    }

    private double getExpandRatio(String pid) {
        if (expandRatioSum < 0) {
            expandRatioSum = 0;
            JsArrayString keyArray = expandRatios.getKeyArray();
            int length = keyArray.length();
            for (int i = 0; i < length; i++) {
                expandRatioSum += expandRatios.getRawNumber(keyArray.get(i));
            }
            if (expandRatioSum == 0) {
                // by default split equally among components
                defaultExpandRatio = 1.0 / widgetToComponentContainer.size();
            } else {
                defaultExpandRatio = 0;
            }
        }
        if (expandRatios.containsKey(pid)) {
            return expandRatios.getRawNumber(pid) / expandRatioSum;
        } else {
            return defaultExpandRatio;
        }
    }

    public void updateCaption(Paintable component, UIDL uidl) {
        ChildComponentContainer componentContainer = getComponentContainer((Widget) component);
        componentContainer.updateCaption(uidl, client);
        if (!isRendering) {
            /*
             * This was a component-only update and the possible size change
             * must be propagated to the layout
             */
            client.captionSizeUpdated(component);
        }
    }

    /**
     * Returns the child component which contains "element". The child component
     * is also returned if "element" is part of its caption.
     * 
     * @param element
     *            An element that is a sub element of the root element in this
     *            layout
     * @return The Paintable which the element is a part of. Null if the element
     *         belongs to the layout and not to a child.
     */
    private Paintable getComponent(Element element) {
        return Util.getChildPaintableForElement(client, this, element);
    }

}
