package org.coderoller.springlayout;

import java.util.Stack;

import org.coderoller.springlayout.LayoutMath.Value;
import org.coderoller.springlayout.LayoutMath.ValueWrapper;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.SparseIntArray;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;

public class SpringLayout extends ViewGroup {
    private static int RELATIVE_SIZE_DENOMINATOR = 100;

    public static final int PARENT = -2;
    public static final int TRUE = -1;

    /**
     * Rule that aligns a child's right edge with another child's left edge.
     */
    public static final int LEFT_OF = 0;
    /**
     * Rule that aligns a child's left edge with another child's right edge.
     */
    public static final int RIGHT_OF = 1;
    /**
     * Rule that aligns a child's bottom edge with another child's top edge.
     */
    public static final int ABOVE = 2;
    /**
     * Rule that aligns a child's top edge with another child's bottom edge.
     */
    public static final int BELOW = 3;
    /**
     * Rule that aligns a child's left edge with another child's left edge.
     */
    public static final int ALIGN_LEFT = 4;
    /**
     * Rule that aligns a child's top edge with another child's top edge.
     */
    public static final int ALIGN_TOP = 5;
    /**
     * Rule that aligns a child's right edge with another child's right edge.
     */
    public static final int ALIGN_RIGHT = 6;
    /**
     * Rule that aligns a child's bottom edge with another child's bottom edge.
     */
    public static final int ALIGN_BOTTOM = 7;
    /**
     * Center will be aligned both horizontally and vertically.
     */
    public static final int ALIGN_CENTER = 8;
    /**
     * Center will be aligned horizontally.
     */
    public static final int ALIGN_CENTER_HORIZONTALLY = 9;
    /**
     * Center will be aligned vertically.
     */
    public static final int ALIGN_CENTER_VERTICALLY = 10;

    /**
     * Rule that aligns the child's left edge with its SpringLayout parent's
     * left edge.
     */
    private static final int ALIGN_PARENT_LEFT = 11;
    /**
     * Rule that aligns the child's top edge with its SpringLayout parent's top
     * edge.
     */
    private static final int ALIGN_PARENT_TOP = 12;
    /**
     * Rule that aligns the child's right edge with its SpringLayout parent's
     * right edge.
     */
    private static final int ALIGN_PARENT_RIGHT = 13;
    /**
     * Rule that aligns the child's bottom edge with its SpringLayout parent's
     * bottom edge.
     */
    private static final int ALIGN_PARENT_BOTTOM = 14;

    /**
     * Rule that centers the child with respect to the bounds of its
     * SpringLayout parent.
     */
    public static final int CENTER_IN_PARENT = 15;
    /**
     * Rule that centers the child horizontally with respect to the bounds of
     * its SpringLayout parent.
     */
    public static final int CENTER_HORIZONTAL = 16;
    /**
     * Rule that centers the child vertically with respect to the bounds of its
     * SpringLayout parent.
     */
    public static final int CENTER_VERTICAL = 17;

    private static final int VERB_COUNT = 18;

    private static int[] VALID_RELATIONS = new int[] { LEFT_OF, RIGHT_OF, ALIGN_LEFT, ALIGN_RIGHT, ABOVE, BELOW, ALIGN_TOP, ALIGN_BOTTOM,
            ALIGN_CENTER_HORIZONTALLY, ALIGN_CENTER_VERTICALLY };
    
    // Constants for error reporting purpose
    private static final int TOP = 0;
    private static final int BOTTOM = 1;
    private static final int LEFT = 2;
    private static final int RIGHT = 3;
    private static final String[] ANCHOR_NAMES = new String[] { "top", "bottom", "left", "right" };

    private ViewConstraints mRootConstraints;
    private final SparseIntArray mIdToViewConstraints = new SparseIntArray();
    private ViewConstraints[] mViewConstraints;
    private final Stack<ViewConstraints> mSpringMetrics = new Stack<ViewConstraints>();
    private final SimpleIdentitySet<ViewConstraints> mHorizontalChains = new SimpleIdentitySet<ViewConstraints>();
    private final SimpleIdentitySet<ViewConstraints> mVerticalChains = new SimpleIdentitySet<ViewConstraints>();

    private LayoutMath mLayoutMath = new LayoutMath();

    private boolean mDirtyHierarchy = true;
    private boolean mDirtySize = true;

    private int mMinWidth = 0, mMinHeight = 0;

    public SpringLayout(Context context) {
        super(context);
    }

    public SpringLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        initFromAttributes(context, attrs);
    }

    public SpringLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initFromAttributes(context, attrs);
    }

    private void initFromAttributes(Context context, AttributeSet attrs) {
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SpringLayout);
        setMinimumWidth(a.getDimensionPixelSize(R.styleable.SpringLayout_minWidth, 0));
        setMinimumHeight(a.getDimensionPixelSize(R.styleable.SpringLayout_minHeight, 0));
        a.recycle();
    }

    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        mDirtyHierarchy = true;
        super.addView(child, index, params);
    }

    @Override
    public void removeView(View view) {
        mDirtyHierarchy = true;
        super.removeView(view);
    }

    @Override
    public void removeViewAt(int index) {
        mDirtyHierarchy = true;
        super.removeViewAt(index);
    }

    @Override
    public void removeViews(int start, int count) {
        mDirtyHierarchy = true;
        super.removeViews(start, count);
    }

    @Override
    public void requestLayout() {
        super.requestLayout();
        mDirtySize = true;

        if (!mDirtyHierarchy) {
            final int count = getChildCount();
            for (int i = 0; i < count; i++) {
                final LayoutParams params = ((LayoutParams) getChildAt(i).getLayoutParams());
                if (params.dirty) {
                    mDirtyHierarchy = true;
                    params.dirty = false;
                }
            }
        }
    }

    private void resizeViewConstraintsArray(int newLen) {
        if (mViewConstraints.length < newLen) {
            ViewConstraints[] oldConstraints = mViewConstraints;
            mViewConstraints = new ViewConstraints[newLen];
            System.arraycopy(oldConstraints, 0, mViewConstraints, 0, oldConstraints.length);
        }
    }

    private void createViewMetrics(Stack<ViewConstraints> springMetrics) {
        springMetrics.clear();
        mIdToViewConstraints.clear();

        if (mRootConstraints != null) {
            mRootConstraints.release();
            for (int i = 0; i < mViewConstraints.length; i++) {
                mViewConstraints[i].release();
            }

            mRootConstraints.reset(this);
            resizeViewConstraintsArray(getChildCount());
        } else {
            mRootConstraints = new ViewConstraints(this, mLayoutMath);
            mViewConstraints = new ViewConstraints[getChildCount()];
        }

        mRootConstraints.left.setValueObject(mLayoutMath.variable(0));
        mRootConstraints.top.setValueObject(mLayoutMath.variable(0));

        final int count = getChildCount();

        for (int i = 0; i < count; i++) {
            final View v = getChildAt(i);
            mIdToViewConstraints.append(v.getId(), i);
            if (mViewConstraints[i] == null) {
                mViewConstraints[i] = new ViewConstraints(v, mLayoutMath);
            } else {
                mViewConstraints[i].reset(v);
            }
        }

        for (int i = 0; i < count; i++) {
            final ViewConstraints viewConstraints = mViewConstraints[i];
            final LayoutParams layoutParams = (LayoutParams) viewConstraints.getView().getLayoutParams();

            if (layoutParams.getWidthWeight() > 0) {
                viewConstraints.markAsHorizontalSpring();
            }

            if (layoutParams.getHeightWeight() > 0) {
                viewConstraints.markAsVerticalSpring();
            }

            int[] childRules = layoutParams.getRelations();
            for (int relation : VALID_RELATIONS) {
                final ViewConstraints metrics = getViewMetrics(childRules[relation]);
                if (metrics != null) {
                    metrics.updateRelation(viewConstraints, relation);
                }
            }
            if (viewConstraints.isHorizontalSpring() || viewConstraints.isVerticalSpring()) {
                springMetrics.add(viewConstraints);
            }
        }
    }

    private ViewConstraints getViewMetrics(int id) {
        if (id == PARENT) {
            return mRootConstraints;
        } else if (id > 0 && mIdToViewConstraints.indexOfKey(id) >= 0) {
            return mViewConstraints[mIdToViewConstraints.get(id)];
        }
        return null;
    }

    private void adaptLayoutParameters() {
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            final LayoutParams childParams = (LayoutParams) child.getLayoutParams();
            int[] relations = childParams.getRelations();

            if (childParams.getWidthWeight() > 0 && childParams.width != LayoutParams.WRAP_CONTENT) {
                throw new IllegalArgumentException("widthWeight > 0 not supported for layout_width != WRAP_CONTENT in View: " + child);
            }

            if (childParams.getHeightWeight() > 0 && childParams.height != LayoutParams.WRAP_CONTENT) {
                throw new IllegalArgumentException("heightWeight > 0 not supported for layout_height != WRAP_CONTENT in View: " + child);
            }

            // If view is aligned both to parent's top and bottom (left and
            // right) then its height (width) is MATCH_PARENT and the other way
            // around
            if (relations[ALIGN_PARENT_TOP] != 0 && relations[ALIGN_PARENT_BOTTOM] != 0) {
                childParams.height = LayoutParams.MATCH_PARENT;
            } else if (childParams.height == LayoutParams.MATCH_PARENT) {
                relations[ALIGN_PARENT_TOP] = relations[ALIGN_PARENT_BOTTOM] = TRUE;
            }

            if (relations[ALIGN_PARENT_LEFT] != 0 && relations[ALIGN_PARENT_RIGHT] != 0) {
                childParams.width = LayoutParams.MATCH_PARENT;
            } else if (childParams.width == LayoutParams.MATCH_PARENT) {
                relations[ALIGN_PARENT_LEFT] = relations[ALIGN_PARENT_RIGHT] = TRUE;
            }

            if (relations[ALIGN_PARENT_TOP] == TRUE) {
                relations[ALIGN_TOP] = PARENT;
            }

            if (relations[ALIGN_PARENT_BOTTOM] == TRUE) {
                relations[ALIGN_BOTTOM] = PARENT;
            }

            if (relations[ALIGN_PARENT_LEFT] == TRUE) {
                relations[ALIGN_LEFT] = PARENT;
            }

            if (relations[ALIGN_PARENT_RIGHT] == TRUE) {
                relations[ALIGN_RIGHT] = PARENT;
            }

            if (relations[ALIGN_CENTER] != 0) {
                relations[ALIGN_CENTER_HORIZONTALLY] = relations[ALIGN_CENTER];
                relations[ALIGN_CENTER_VERTICALLY] = relations[ALIGN_CENTER];
            }

            if (relations[CENTER_IN_PARENT] == TRUE) {
                relations[CENTER_HORIZONTAL] = relations[CENTER_VERTICAL] = TRUE;
            }

            if (relations[CENTER_HORIZONTAL] == TRUE) {
                relations[ALIGN_CENTER_HORIZONTALLY] = PARENT;
            }

            if (relations[CENTER_VERTICAL] == TRUE) {
                relations[ALIGN_CENTER_VERTICALLY] = PARENT;
            }

            if (!hasHorizontalRelations(relations)) {
                relations[ALIGN_LEFT] = PARENT;
            }

            if (!hasVerticalRelations(relations)) {
                relations[ALIGN_TOP] = PARENT;
            }
        }
    }

    private boolean hasHorizontalRelations(int[] relations) {
        return relations[LEFT_OF] != 0 || relations[RIGHT_OF] != 0 || relations[ALIGN_LEFT] != 0 || relations[ALIGN_RIGHT] != 0
                || relations[ALIGN_CENTER_HORIZONTALLY] != 0;
    }

    private boolean hasVerticalRelations(int[] relations) {
        return relations[BELOW] != 0 || relations[ABOVE] != 0 || relations[ALIGN_TOP] != 0 || relations[ALIGN_BOTTOM] != 0
                || relations[ALIGN_CENTER_VERTICALLY] != 0;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int myWidth = -1;
        int myHeight = -1;
        int width = 0;
        int height = 0;

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        final boolean isWrapContentWidth = widthMode != MeasureSpec.EXACTLY;
        final boolean isWrapContentHeight = heightMode != MeasureSpec.EXACTLY;

        if (mDirtyHierarchy) {
            mDirtyHierarchy = false;
            adaptLayoutParameters();
            createViewMetrics(mSpringMetrics);
            handleSprings(mSpringMetrics, isWrapContentWidth, isWrapContentHeight);
        }

        // Record our dimensions if they are known;
        if (widthMode != MeasureSpec.UNSPECIFIED) {
            myWidth = widthSize;
        }

        if (heightMode != MeasureSpec.UNSPECIFIED) {
            myHeight = heightSize;
        }

        if (widthMode == MeasureSpec.EXACTLY) {
            width = myWidth;
        }

        if (heightMode == MeasureSpec.EXACTLY) {
            height = myHeight;
        }

        if (mDirtySize) {
            mDirtySize = false;
            invalidateMathCache();
            updateChildrenSize(widthMeasureSpec, heightMeasureSpec);
            updateLayoutSize(isWrapContentWidth, width, isWrapContentHeight, height);
            cacheLayoutPositions();
        }

        setMeasuredDimension(mRootConstraints.right.getValue(), mRootConstraints.bottom.getValue());
    }

    private void invalidateMathCache() {
        mRootConstraints.invalidate();
        for (int i = 0; i < getChildCount(); i++) {
            final ViewConstraints viewConstraints = mViewConstraints[i];
            viewConstraints.invalidate();
        }
    }

    private void updateChildrenSize(final int widthMeasureSpec, final int heightMeasureSpec) {
        for (int i = 0; i < getChildCount(); i++) {
            final ViewConstraints viewConstraints = mViewConstraints[i];
            final View v = viewConstraints.getView();
            final LayoutParams layoutParams = (LayoutParams) v.getLayoutParams();
            final int mL = layoutParams.leftMargin, mR = layoutParams.rightMargin, mT = layoutParams.topMargin, mB = layoutParams.bottomMargin;
            measureChildWithMargins(v, widthMeasureSpec, 0, heightMeasureSpec, 0);

            if (!viewConstraints.isHorizontalSpring()) {
                Value childWidth;
                if (v.getVisibility() == View.GONE) {
                    childWidth = mLayoutMath.variable(0);
                } else if (layoutParams.relativeWidth > 0) {
                    childWidth = mRootConstraints.innerRight.subtract(mRootConstraints.innerLeft)
                            .multiply(mLayoutMath.variable(layoutParams.relativeWidth))
                            .divide(mLayoutMath.variable(RELATIVE_SIZE_DENOMINATOR));
                } else {
                    childWidth = mLayoutMath.variable(v.getMeasuredWidth());
                }

                viewConstraints.leftMargin.setValue(mL);
                viewConstraints.rightMargin.setValue(mR);

                Value outerWidth = childWidth.add(mLayoutMath.variable(mL + mR)).retain();
                viewConstraints.setWidth(outerWidth);
                outerWidth.release();
            }

            if (!viewConstraints.isVerticalSpring()) {
                Value childHeight;
                if (v.getVisibility() == View.GONE) {
                    childHeight = mLayoutMath.variable(0);
                } else if (layoutParams.relativeHeight > 0) {
                    childHeight = mRootConstraints.innerBottom.subtract(mRootConstraints.innerTop)
                            .multiply(mLayoutMath.variable(layoutParams.relativeHeight))
                            .divide(mLayoutMath.variable(RELATIVE_SIZE_DENOMINATOR));
                } else {
                    childHeight = mLayoutMath.variable(v.getMeasuredHeight());
                }

                viewConstraints.topMargin.setValue(mT);
                viewConstraints.bottomMargin.setValue(mB);

                Value outerHeight = childHeight.add(mLayoutMath.variable(mT + mB)).retain();
                viewConstraints.setHeight(outerHeight);
                outerHeight.release();
            }
        }
    }

    private void handleSprings(final Stack<ViewConstraints> springMetrics, final boolean isWrapContentWidth,
            final boolean isWrapContentHeight) {
        if (!springMetrics.isEmpty()) {
            mHorizontalChains.clear();
            mVerticalChains.clear();
            while (!springMetrics.isEmpty()) {
                final ViewConstraints spring = springMetrics.pop();
                final ViewConstraints chainHeadX = getChainHorizontalHead(spring);
                final ViewConstraints chainHeadY = getChainVerticalHead(spring);
                if (chainHeadX != null) {
                    if (isWrapContentWidth && mMinWidth <= 0) {
                        throw new IllegalStateException("Horizontal springs not supported when layout width is wrap_content");
                    }
                    mHorizontalChains.add(chainHeadX);
                }
                if (chainHeadY != null) {
                    if (isWrapContentHeight && mMinHeight <= 0) {
                        throw new IllegalStateException(
                                "Vertical springs not supported when layout height is wrap_content and minHeight is not defined");
                    }
                    mVerticalChains.add(chainHeadY);
                }
            }

            for (int i = 0; i < mHorizontalChains.size(); i++) {
                final ViewConstraints chainHead = mHorizontalChains.get(i);
                int totalWeight = 0;
                Value contentWidth = mLayoutMath.variable(0);
                final ValueWrapper totalWeightWrapper = mLayoutMath.wrap();
                final ValueWrapper chainWidthWrapper = mLayoutMath.wrap();
                ViewConstraints chainElem = chainHead, prevElem = null;
                Value start = chainElem.left, end;
                while (chainElem != null) {
                    if (chainElem.isHorizontalSpring()) {
                        chainElem.markHorizontalSpringUsed();
                        final int weight = ((LayoutParams) chainElem.getView().getLayoutParams()).widthWeight;
                        totalWeight += weight;
                        final Value width = chainWidthWrapper.multiply(mLayoutMath.variable(weight)).divide(totalWeightWrapper)
                                .max(mLayoutMath.variable(0)).retain();
                        chainElem.setWidth(width);
                        width.release();
                    } else {
                        contentWidth = contentWidth.add(chainElem.getWidth());
                    }
                    prevElem = chainElem;
                    chainElem = chainElem.nextX;
                }
                end = prevElem.right;
                totalWeightWrapper.setValueObject(mLayoutMath.variable(totalWeight));
                chainWidthWrapper.setValueObject(end.subtract(start).subtract(contentWidth));
            }

            for (int i = 0; i < mVerticalChains.size(); i++) {
                final ViewConstraints chainHead = mVerticalChains.get(i);
                int totalWeight = 0;
                Value contentHeight = mLayoutMath.variable(0);
                final ValueWrapper totalWeightWrapper = mLayoutMath.wrap();
                final ValueWrapper chainWidthWrapper = mLayoutMath.wrap();
                ViewConstraints chainElem = chainHead, prevElem = null;
                Value start = chainElem.top, end;
                while (chainElem != null) {
                    if (chainElem.isVerticalSpring()) {
                        chainElem.markVerticalSpringUsed();
                        final int weight = ((LayoutParams) chainElem.getView().getLayoutParams()).heightWeight;
                        totalWeight += weight;
                        final Value height = chainWidthWrapper.multiply(mLayoutMath.variable(weight)).divide(totalWeightWrapper)
                                .max(mLayoutMath.variable(0)).retain();
                        chainElem.setHeight(height);
                        height.release();
                    } else {
                        contentHeight = contentHeight.add(chainElem.getHeight());
                    }
                    prevElem = chainElem;
                    chainElem = chainElem.nextY;
                }
                end = prevElem.bottom;
                totalWeightWrapper.setValueObject(mLayoutMath.variable(totalWeight));
                chainWidthWrapper.setValueObject(end.subtract(start).subtract(contentHeight));
            }
        }
    }

    private void updateLayoutSize(final boolean isWrapContentWidth, int width, final boolean isWrapContentHeight, int height) {
        final int pL = getPaddingLeft(), pR = getPaddingRight(), pT = getPaddingTop(), pB = getPaddingBottom();

        mRootConstraints.leftMargin.setValue(pL);
        mRootConstraints.rightMargin.setValue(pR);
        mRootConstraints.topMargin.setValue(pT);
        mRootConstraints.bottomMargin.setValue(pB);

        Value widthValue, heightValue;

        if (isWrapContentWidth) {
            int maxSize = mMinWidth > 0 ? mMinWidth : -1;
            for (int i = 0; i < getChildCount(); i++) {
                final ViewConstraints viewConstraints = mViewConstraints[i];
                try {
                    maxSize = Math.max(maxSize, viewConstraints.right.getValue() + pR);
                    viewConstraints.right.invalidate();
                } catch (IllegalStateException e) {
                }
            }
            if (maxSize < 0) {
                throw new IllegalStateException(
                        "Parent layout_width == wrap_content is not supported if width of all children depends on parent width.");
            }
            widthValue = mLayoutMath.variable(maxSize);
        } else {
            widthValue = mLayoutMath.variable(width);
        }
        mRootConstraints.right.setValueObject(widthValue);

        if (isWrapContentHeight) {
            int maxSize = mMinHeight > 0 ? mMinHeight : -1;
            for (int i = 0; i < getChildCount(); i++) {
                final ViewConstraints viewConstraints = mViewConstraints[i];
                try {
                    maxSize = Math.max(maxSize, viewConstraints.bottom.getValue() + pB);
                    viewConstraints.bottom.invalidate();
                } catch (IllegalStateException e) {
                }
            }
            if (maxSize < 0) {
                throw new IllegalStateException(
                        "Parent layout_height == wrap_content is not supported if height of all children depends on parent height.");
            }
            heightValue = mLayoutMath.variable(maxSize);
        } else {
            heightValue = mLayoutMath.variable(height);
        }
        mRootConstraints.bottom.setValueObject(heightValue);
    }

    private void cacheLayoutPositions() {
        for (int i = 0; i < getChildCount(); i++) {
            final ViewConstraints viewConstraints = mViewConstraints[i];
            final View v = viewConstraints.getView();
            if (viewConstraints.isHorizontalSpring() && !viewConstraints.isHorizontalSpringUsed()) {
                throw new IllegalStateException(
                        "Horizontal weight defined but never used, please review your layout. Remember that the chain of views cannot divert when using springs: Problematic view (please also check other dependant views): "
                                + v + ", problematic layout: " + this);
            } else if (viewConstraints.isVerticalSpring() && !viewConstraints.isVerticalSpringUsed()) {
                throw new IllegalStateException(
                        "Vertical weight defined but never used, please review your layout. Remember that the chain of views cannot divert when using springs: Problematic view (please also check other dependant views): "
                                + v + ", problematic layout: " + this);
            } else {
                int anchor = 0;
                try {
                    SpringLayout.LayoutParams st = (SpringLayout.LayoutParams) v.getLayoutParams();
                    anchor = LEFT;
                    st.left = viewConstraints.innerLeft.getValue();
                    anchor = RIGHT;
                    st.right = viewConstraints.innerRight.getValue();
                    anchor = TOP;
                    st.top = viewConstraints.innerTop.getValue();
                    anchor = BOTTOM;
                    st.bottom = viewConstraints.innerBottom.getValue();
                    v.measure(MeasureSpec.makeMeasureSpec(st.right - st.left, MeasureSpec.EXACTLY),
                            MeasureSpec.makeMeasureSpec(st.bottom - st.top, MeasureSpec.EXACTLY));
                } catch (IllegalStateException e) {
                    throw new IllegalStateException("View " + ANCHOR_NAMES[anchor] + " position could not be calculated, please review your layout. Remember that A.above = B and B.below = A are not equivalent in terms of calculation order, please refer to documentation. Problematic view (please also check other dependant views): "
                                    + v + ", problematic layout: " + this, e);
                } catch (StackOverflowError e) {
                    throw new IllegalStateException(
                            "Constraints of a view could not be resolved (circular dependency), please review your layout. Problematic view (please also check other dependant views): "
                                    + v + ", problematic layout: " + this);
                }
            }
        }
    }

    private ViewConstraints getChainVerticalHead(ViewConstraints spring) {
        if (spring.nextY == null && spring.prevY == null) {
            return null;
        } else {
            while (spring.prevY != null) {
                spring = spring.prevY;
            }
            return spring;
        }
    }

    private ViewConstraints getChainHorizontalHead(ViewConstraints spring) {
        if (spring.nextX == null && spring.prevX == null) {
            return null;
        } else {
            while (spring.prevX != null) {
                spring = spring.prevX;
            }
            return spring;
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            if (child.getVisibility() != View.GONE) {
                SpringLayout.LayoutParams st = (SpringLayout.LayoutParams) child.getLayoutParams();
                child.layout(st.left, st.top, st.right, st.bottom);
            }
        }
    }

    @Override
    public void setMinimumHeight(int minHeight) {
        super.setMinimumHeight(minHeight);
        mMinHeight = minHeight;
    }

    @Override
    public void setMinimumWidth(int minWidth) {
        super.setMinimumWidth(minWidth);
        mMinWidth = minWidth;
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new SpringLayout.LayoutParams(getContext(), attrs);
    }

    /**
     * Returns a set of layout parameters with a width of
     * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}, a height of
     * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and no spanning.
     */
    @Override
    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    }

    // Override to allow type-checking of LayoutParams.
    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof SpringLayout.LayoutParams;
    }

    @Override
    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return new LayoutParams(p);
    }

    /**
     * Per-child layout information associated with SpringLayout.
     * 
     * @attr ref R.styleable#SpringLayout_Layout_layout_toLeftOf
     * @attr ref R.styleable#SpringLayout_Layout_layout_toRightOf
     * @attr ref R.styleable#SpringLayout_Layout_layout_above
     * @attr ref R.styleable#SpringLayout_Layout_layout_below
     * @attr ref R.styleable#SpringLayout_Layout_layout_alignBaseline
     * @attr ref R.styleable#SpringLayout_Layout_layout_alignLeft
     * @attr ref R.styleable#SpringLayout_Layout_layout_alignTop
     * @attr ref R.styleable#SpringLayout_Layout_layout_alignRight
     * @attr ref R.styleable#SpringLayout_Layout_layout_alignBottom
     * @attr ref R.styleable#SpringLayout_Layout_layout_alignCenterHorizontally
     * @attr ref R.styleable#SpringLayout_Layout_layout_alignCenterVertically
     * @attr ref R.styleable#SpringLayout_Layout_layout_alignParentLeft
     * @attr ref R.styleable#SpringLayout_Layout_layout_alignParentTop
     * @attr ref R.styleable#SpringLayout_Layout_layout_alignParentRight
     * @attr ref R.styleable#SpringLayout_Layout_layout_alignParentBottom
     * @attr ref R.styleable#SpringLayout_Layout_layout_centerInParent
     * @attr ref R.styleable#SpringLayout_Layout_layout_centerHorizontal
     * @attr ref R.styleable#SpringLayout_Layout_layout_centerVertical
     */
    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
        @ViewDebug.ExportedProperty(resolveId = true, indexMapping = { @ViewDebug.IntToString(from = ABOVE, to = "above"),
                @ViewDebug.IntToString(from = BELOW, to = "below"), @ViewDebug.IntToString(from = LEFT_OF, to = "leftOf"),
                @ViewDebug.IntToString(from = RIGHT_OF, to = "rightOf"),
                @ViewDebug.IntToString(from = ALIGN_PARENT_LEFT, to = "alignParentLeft"),
                @ViewDebug.IntToString(from = ALIGN_PARENT_RIGHT, to = "alignParentRight"),
                @ViewDebug.IntToString(from = ALIGN_PARENT_TOP, to = "alignParentTop"),
                @ViewDebug.IntToString(from = ALIGN_PARENT_BOTTOM, to = "alignParentBottom"),
                @ViewDebug.IntToString(from = ALIGN_LEFT, to = "alignLeft"), @ViewDebug.IntToString(from = ALIGN_RIGHT, to = "alignRight"),
                @ViewDebug.IntToString(from = ALIGN_TOP, to = "alignTop"), @ViewDebug.IntToString(from = ALIGN_BOTTOM, to = "alignBottom"),
                @ViewDebug.IntToString(from = ALIGN_CENTER, to = "alignCenter"),
                @ViewDebug.IntToString(from = ALIGN_CENTER_HORIZONTALLY, to = "alignCenterHorizontally"),
                @ViewDebug.IntToString(from = ALIGN_CENTER_VERTICALLY, to = "alignCenterVertically"),
                @ViewDebug.IntToString(from = CENTER_HORIZONTAL, to = "centerHorizontal"),
                @ViewDebug.IntToString(from = CENTER_IN_PARENT, to = "centerInParent"),
                @ViewDebug.IntToString(from = CENTER_VERTICAL, to = "centerVertical"), }, mapping = {
                @ViewDebug.IntToString(from = TRUE, to = "true"), @ViewDebug.IntToString(from = 0, to = "false/NO_ID"),
                @ViewDebug.IntToString(from = PARENT, to = "parent") })
        int[] relations = new int[VERB_COUNT];
        int left, top, right, bottom;
        int relativeHeight, relativeWidth;
        int heightWeight = 0, widthWeight = 0;
        boolean dirty = true;

        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);

            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.SpringLayout_Layout);

            final int[] relations = this.relations;

            final int N = a.getIndexCount();
            for (int i = 0; i < N; i++) {
                int attr = a.getIndex(i);
                switch (attr) {
                case R.styleable.SpringLayout_Layout_layout_toLeftOf:
                    relations[LEFT_OF] = a.getResourceId(attr, 0);
                    break;
                case R.styleable.SpringLayout_Layout_layout_toRightOf:
                    relations[RIGHT_OF] = a.getResourceId(attr, 0);
                    break;
                case R.styleable.SpringLayout_Layout_layout_above:
                    relations[ABOVE] = a.getResourceId(attr, 0);
                    break;
                case R.styleable.SpringLayout_Layout_layout_below:
                    relations[BELOW] = a.getResourceId(attr, 0);
                    break;
                case R.styleable.SpringLayout_Layout_layout_alignLeft:
                    relations[ALIGN_LEFT] = a.getResourceId(attr, 0);
                    break;
                case R.styleable.SpringLayout_Layout_layout_alignTop:
                    relations[ALIGN_TOP] = a.getResourceId(attr, 0);
                    break;
                case R.styleable.SpringLayout_Layout_layout_alignRight:
                    relations[ALIGN_RIGHT] = a.getResourceId(attr, 0);
                    break;
                case R.styleable.SpringLayout_Layout_layout_alignBottom:
                    relations[ALIGN_BOTTOM] = a.getResourceId(attr, 0);
                    break;
                case R.styleable.SpringLayout_Layout_layout_alignCenter:
                    relations[ALIGN_CENTER] = a.getResourceId(attr, 0);
                    break;
                case R.styleable.SpringLayout_Layout_layout_alignCenterHorizontally:
                    relations[ALIGN_CENTER_HORIZONTALLY] = a.getResourceId(attr, 0);
                    break;
                case R.styleable.SpringLayout_Layout_layout_alignCenterVertically:
                    relations[ALIGN_CENTER_VERTICALLY] = a.getResourceId(attr, 0);
                    break;
                case R.styleable.SpringLayout_Layout_layout_alignParentLeft:
                    relations[ALIGN_PARENT_LEFT] = a.getBoolean(attr, false) ? TRUE : 0;
                    break;
                case R.styleable.SpringLayout_Layout_layout_alignParentTop:
                    relations[ALIGN_PARENT_TOP] = a.getBoolean(attr, false) ? TRUE : 0;
                    break;
                case R.styleable.SpringLayout_Layout_layout_alignParentRight:
                    relations[ALIGN_PARENT_RIGHT] = a.getBoolean(attr, false) ? TRUE : 0;
                    break;
                case R.styleable.SpringLayout_Layout_layout_alignParentBottom:
                    relations[ALIGN_PARENT_BOTTOM] = a.getBoolean(attr, false) ? TRUE : 0;
                    break;
                case R.styleable.SpringLayout_Layout_layout_centerInParent:
                    relations[CENTER_IN_PARENT] = a.getBoolean(attr, false) ? TRUE : 0;
                    break;
                case R.styleable.SpringLayout_Layout_layout_centerHorizontal:
                    relations[CENTER_HORIZONTAL] = a.getBoolean(attr, false) ? TRUE : 0;
                    break;
                case R.styleable.SpringLayout_Layout_layout_centerVertical:
                    relations[CENTER_VERTICAL] = a.getBoolean(attr, false) ? TRUE : 0;
                    break;
                case R.styleable.SpringLayout_Layout_layout_relativeWidth:
                    relativeWidth = (int) a.getFraction(attr, RELATIVE_SIZE_DENOMINATOR, 1, 0);
                    break;
                case R.styleable.SpringLayout_Layout_layout_relativeHeight:
                    relativeHeight = (int) a.getFraction(attr, RELATIVE_SIZE_DENOMINATOR, 1, 0);
                    break;
                case R.styleable.SpringLayout_Layout_layout_widthWeight:
                    widthWeight = a.getInteger(attr, 0);
                    break;
                case R.styleable.SpringLayout_Layout_layout_heightWeight:
                    heightWeight = a.getInteger(attr, 0);
                    break;
                }
            }

            a.recycle();
        }

        public LayoutParams(int w, int h) {
            super(w, h);
        }

        /**
         * {@inheritDoc}
         */
        public LayoutParams(ViewGroup.LayoutParams source) {
            super(source);
        }

        /**
         * {@inheritDoc}
         */
        public LayoutParams(ViewGroup.MarginLayoutParams source) {
            super(source);
        }

        public void addRelation(int relation, int anchor) {
            relations[relation] = anchor;
            dirty = true;
        }

        /**
         * Retrieves a complete list of all supported relations, where the index
         * is the relation verb, and the element value is the value specified,
         * or "false" if it was never set.
         * 
         * @return the supported relations
         * @see #addRelation(int, int)
         */
        public int[] getRelations() {
            return relations;
        }

        public int getRelativeHeight() {
            return relativeHeight;
        }

        public void setRelativeHeight(int relativeHeight) {
            this.relativeHeight = relativeHeight;
        }

        public int getRelativeWidth() {
            return relativeWidth;
        }

        public void setRelativeWidth(int relativeWidth) {
            dirty = true;
            this.relativeWidth = relativeWidth;
        }

        public int getWidthWeight() {
            return widthWeight;
        }

        public void setWidthWeight(int widthWeight) {
            dirty = true;
            this.widthWeight = widthWeight;
        }

        public int getHeightWeight() {
            return heightWeight;
        }

        public void setHeightWeight(int heightWeight) {
            dirty = true;
            this.heightWeight = heightWeight;
        }

        public void setWidth(int width) {
            if (this.width != width) {
                if (width == MATCH_PARENT || this.width == MATCH_PARENT) {
                    dirty = true;
                }
                if (width != WRAP_CONTENT || this.width == WRAP_CONTENT) {
                    this.widthWeight = 0;
                    dirty = true;
                }
                this.width = width;
            }
        }

        public void setHeight(int height) {
            if (this.height != height) {
                if (height == MATCH_PARENT || this.height == MATCH_PARENT) {
                    dirty = true;
                }
                if (height != WRAP_CONTENT || this.height == WRAP_CONTENT) {
                    this.heightWeight = 0;
                    dirty = true;
                }
                this.height = height;
            }
        }
    }
}