/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License
 */

package android.support.wearable.view;

import com.android.cts.verifier.R;

import android.annotation.TargetApi;
import android.os.Build;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.widget.FrameLayout;

/**
 * BoxInsetLayout is a screen shape-aware FrameLayout that can box its children
 * in the center square of a round screen by using the
 * {@code layout_box} attribute. The values for this attribute specify the
 * child's edges to be boxed in:
 * {@code left|top|right|bottom} or {@code all}.
 * The {@code layout_box} attribute is ignored on a device with a rectangular
 * screen.
 */
@TargetApi(Build.VERSION_CODES.KITKAT_WATCH)
public class BoxInsetLayout extends FrameLayout {

    private static float FACTOR = 0.146467f; //(1 - sqrt(2)/2)/2
    private static final int DEFAULT_CHILD_GRAVITY = Gravity.TOP | Gravity.START;

    private Rect mForegroundPadding;
    private boolean mLastKnownRound;
    private Rect mInsets;

    public BoxInsetLayout(Context context) {
        this(context, null);
    }

    public BoxInsetLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BoxInsetLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        // make sure we have foreground padding object
        if (mForegroundPadding == null) {
            mForegroundPadding = new Rect();
        }
        if (mInsets == null) {
            mInsets = new Rect();
        }
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        requestApplyInsets();
    }

    @Override
    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
        insets = super.onApplyWindowInsets(insets);
        final boolean round = insets.isRound();
        if (round != mLastKnownRound) {
            mLastKnownRound = round;
            requestLayout();
        }
        mInsets.set(
            insets.getSystemWindowInsetLeft(),
            insets.getSystemWindowInsetTop(),
            insets.getSystemWindowInsetRight(),
            insets.getSystemWindowInsetBottom());
        return insets;
    }

    /**
     * determine screen shape
     * @return true if on a round screen
     */
    public boolean isRound() {
        return mLastKnownRound;
    }

    /**
     * @return the system window insets Rect
     */
    public Rect getInsets() {
        return mInsets;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();
        // find max size
        int maxWidth = 0;
        int maxHeight = 0;
        int childState = 0;
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                LayoutParams lp = (BoxInsetLayout.LayoutParams) child.getLayoutParams();
                int marginLeft = 0;
                int marginRight = 0;
                int marginTop = 0;
                int marginBottom = 0;
                if (mLastKnownRound) {
                    // round screen, check boxed, don't use margins on boxed
                    if ((lp.boxedEdges & LayoutParams.BOX_LEFT) == 0) {
                        marginLeft = lp.leftMargin;
                    }
                    if ((lp.boxedEdges & LayoutParams.BOX_RIGHT) == 0) {
                        marginRight = lp.rightMargin;
                    }
                    if ((lp.boxedEdges & LayoutParams.BOX_TOP) == 0) {
                        marginTop = lp.topMargin;
                    }
                    if ((lp.boxedEdges & LayoutParams.BOX_BOTTOM) == 0) {
                        marginBottom = lp.bottomMargin;
                    }
                } else {
                    // rectangular, ignore boxed, use margins
                    marginLeft = lp.leftMargin;
                    marginTop = lp.topMargin;
                    marginRight = lp.rightMargin;
                    marginBottom = lp.bottomMargin;
                }
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                maxWidth = Math.max(maxWidth,
                        child.getMeasuredWidth() + marginLeft + marginRight);
                maxHeight = Math.max(maxHeight,
                        child.getMeasuredHeight() + marginTop + marginBottom);
                childState = combineMeasuredStates(childState, child.getMeasuredState());
            }
        }
        // Account for padding too
        maxWidth += getPaddingLeft() + mForegroundPadding.left
                + getPaddingRight() + mForegroundPadding.right;
        maxHeight += getPaddingTop() + mForegroundPadding.top
                + getPaddingBottom() + mForegroundPadding.bottom;

        // Check against our minimum height and width
        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

        // Check against our foreground's minimum height and width
        final Drawable drawable = getForeground();
        if (drawable != null) {
            maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
            maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
        }

        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));

        // determine boxed inset
        int boxInset = (int) (FACTOR * Math.max(getMeasuredWidth(), getMeasuredHeight()));
        // adjust the match parent children
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);

            final LayoutParams lp = (BoxInsetLayout.LayoutParams) child.getLayoutParams();
            int childWidthMeasureSpec;
            int childHeightMeasureSpec;
            int plwf = getPaddingLeft() + mForegroundPadding.left;
            int prwf = getPaddingRight() + mForegroundPadding.right;
            int ptwf = getPaddingTop() + mForegroundPadding.top;
            int pbwf = getPaddingBottom() + mForegroundPadding.bottom;

            // adjust width
            int totalPadding = 0;
            int totalMargin = 0;
            // BoxInset is a padding. Ignore margin when we want to do BoxInset.
            if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_LEFT) != 0)) {
                totalPadding += boxInset;
            } else {
                totalMargin += plwf + lp.leftMargin;
            }
            if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_RIGHT) != 0)) {
                totalPadding += boxInset;
            } else {
                totalMargin += prwf + lp.rightMargin;
            }
            if (lp.width == LayoutParams.MATCH_PARENT) {
                //  Only subtract margin from the actual width, leave the padding in.
                childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                        getMeasuredWidth() - totalMargin, MeasureSpec.EXACTLY);
            } else {
                childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                        totalPadding + totalMargin, lp.width);
            }

            // adjust height
            totalPadding = 0;
            totalMargin = 0;
            if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_TOP) != 0)) {
                totalPadding += boxInset;
            } else {
                totalMargin += ptwf + lp.topMargin;
            }
            if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_BOTTOM) != 0)) {
                totalPadding += boxInset;
            } else {
                totalMargin += pbwf + lp.bottomMargin;
            }

            if (lp.height == LayoutParams.MATCH_PARENT) {
                childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                        getMeasuredHeight() - totalMargin, MeasureSpec.EXACTLY);
            } else {
                childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
                        totalPadding + totalMargin, lp.height);
            }

            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    }


    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        layoutBoxChildren(left, top, right, bottom, false /* no force left gravity */);
    }

    private void layoutBoxChildren(int left, int top, int right, int bottom,
                                  boolean forceLeftGravity) {
        final int count = getChildCount();
        int boxInset = (int)(FACTOR * Math.max(right - left, bottom - top));

        final int parentLeft = getPaddingLeft() + mForegroundPadding.left;
        final int parentRight = right - left - getPaddingRight() - mForegroundPadding.right;

        final int parentTop = getPaddingTop() + mForegroundPadding.top;
        final int parentBottom = bottom - top - getPaddingBottom() - mForegroundPadding.bottom;

        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();

                final int width = child.getMeasuredWidth();
                final int height = child.getMeasuredHeight();

                int childLeft;
                int childTop;

                int gravity = lp.gravity;
                if (gravity == -1) {
                    gravity = DEFAULT_CHILD_GRAVITY;
                }

                final int layoutDirection = getLayoutDirection();
                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;

                // These values are replaced with boxInset below as necessary.
                int paddingLeft = child.getPaddingLeft();
                int paddingRight = child.getPaddingRight();
                int paddingTop = child.getPaddingTop();
                int paddingBottom = child.getPaddingBottom();

                // If the child's width is match_parent, we ignore gravity and set boxInset padding
                // on both sides, with a left position of parentLeft + the child's left margin.
                if (lp.width == LayoutParams.MATCH_PARENT) {
                    if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_LEFT) != 0)) {
                        paddingLeft = boxInset;
                    }
                    if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_RIGHT) != 0)) {
                        paddingRight = boxInset;
                    }
                    childLeft = parentLeft + lp.leftMargin;
                } else {
                    switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                        case Gravity.CENTER_HORIZONTAL:
                            childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                                    lp.leftMargin - lp.rightMargin;
                            break;
                        case Gravity.RIGHT:
                            if (!forceLeftGravity) {
                                if (mLastKnownRound
                                        && ((lp.boxedEdges & LayoutParams.BOX_RIGHT) != 0)) {
                                    paddingRight = boxInset;
                                    childLeft = right - left - width;
                                } else {
                                    childLeft = parentRight - width - lp.rightMargin;
                                }
                                break;
                            }
                        case Gravity.LEFT:
                        default:
                            if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_LEFT) != 0)) {
                                paddingLeft = boxInset;
                                childLeft = 0;
                            } else {
                                childLeft = parentLeft + lp.leftMargin;
                            }
                    }
                }

                // If the child's height is match_parent, we ignore gravity and set boxInset padding
                // on both top and bottom, with a top position of parentTop + the child's top
                // margin.
                if (lp.height == LayoutParams.MATCH_PARENT) {
                    if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_TOP) != 0)) {
                        paddingTop = boxInset;
                    }
                    if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_BOTTOM) != 0)) {
                        paddingBottom = boxInset;
                    }
                    childTop = parentTop + lp.topMargin;
                } else {
                    switch (verticalGravity) {
                        case Gravity.TOP:
                            if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_TOP) != 0)) {
                                paddingTop = boxInset;
                                childTop = 0;
                            } else {
                                childTop = parentTop + lp.topMargin;
                            }
                            break;
                        case Gravity.CENTER_VERTICAL:
                            childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                                    lp.topMargin - lp.bottomMargin;
                            break;
                        case Gravity.BOTTOM:
                            if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_BOTTOM) != 0)) {
                                paddingBottom = boxInset;
                                childTop = bottom - top - height;
                            } else {
                                childTop = parentBottom - height - lp.bottomMargin;
                            }
                            break;
                        default:
                            childTop = parentTop + lp.topMargin;
                    }
                }

                child.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
                child.layout(childLeft, childTop, childLeft + width, childTop + height);
            }
        }
    }

    public void setForeground(Drawable drawable) {
        super.setForeground(drawable);
        if (mForegroundPadding == null) {
            mForegroundPadding = new Rect();
        }
        drawable.getPadding(mForegroundPadding);
    }

    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof LayoutParams;
    }

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

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

    /**
     * adds {@code layout_box} attribute to layout parameters
     */
    public static class LayoutParams extends FrameLayout.LayoutParams {

        public static final int BOX_NONE = 0x0;
        public static final int BOX_LEFT = 0x01;
        public static final int BOX_TOP = 0x02;
        public static final int BOX_RIGHT = 0x04;
        public static final int BOX_BOTTOM = 0x08;
        public static final int BOX_ALL = 0x0F;

        public int boxedEdges = BOX_NONE;

        public LayoutParams(Context context, AttributeSet attrs) {
            super(context, attrs);
            TypedArray a = context.obtainStyledAttributes(attrs,  R.styleable.BoxInsetLayout_Layout, 0, 0);
            boxedEdges = a.getInt(R.styleable.BoxInsetLayout_Layout_layout_box, BOX_NONE);
            a.recycle();
        }

        public LayoutParams(int width, int height) {
            super(width, height);
        }

        public LayoutParams(int width, int height, int gravity) {
            super(width, height, gravity);
        }

        public LayoutParams(int width, int height, int gravity, int boxed) {
            super(width, height, gravity);
            boxedEdges = boxed;
        }

        public LayoutParams(ViewGroup.LayoutParams source) {
            super(source);
        }

        public LayoutParams(ViewGroup.MarginLayoutParams source) {
            super(source);
        }

        public LayoutParams(FrameLayout.LayoutParams source) {
            super(source);
        }

        public LayoutParams(LayoutParams source) {
            super(source);
            this.boxedEdges = source.boxedEdges;
            this.gravity = source.gravity;
        }

    }
}
