/*
 * 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.v17.leanback.widget;

import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_LOW_EDGE;
import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_HIGH_EDGE;
import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_BOTH_EDGE;
import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED;

import static android.support.v7.widget.RecyclerView.HORIZONTAL;

import android.view.View;

/**
 * Maintains Window Alignment information of two axis.
 */
class WindowAlignment {

    /**
     * Maintains alignment information in one direction.
     */
    public static class Axis {
        /**
         * mScrollCenter is used to calculate dynamic transformation based on how far a view
         * is from the mScrollCenter. For example, the views with center close to mScrollCenter
         * will be scaled up.
         */
        private float mScrollCenter;
        /** 
         * Right or bottom edge of last child. 
         */
        private int mMaxEdge;
        /** 
         * Left or top edge of first child, typically should be zero.
         */
        private int mMinEdge;
        /**
         * Max Scroll value
         */
        private int mMaxScroll;
        /**
         * Min Scroll value
         */
        private int mMinScroll;

        private int mWindowAlignment = WINDOW_ALIGN_BOTH_EDGE;

        private int mWindowAlignmentOffset = 0;

        private float mWindowAlignmentOffsetPercent = 50f;

        private int mSize;

        private int mPaddingLow;

        private int mPaddingHigh;

        private boolean mReversedFlow;

        private String mName; // for debugging

        public Axis(String name) {
            reset();
            mName = name;
        }

        final public int getWindowAlignment() {
            return mWindowAlignment;
        }

        final public void setWindowAlignment(int windowAlignment) {
            mWindowAlignment = windowAlignment;
        }

        final public int getWindowAlignmentOffset() {
            return mWindowAlignmentOffset;
        }

        final public void setWindowAlignmentOffset(int offset) {
            mWindowAlignmentOffset = offset;
        }

        final public void setWindowAlignmentOffsetPercent(float percent) {
            if ((percent < 0 || percent > 100)
                    && percent != WINDOW_ALIGN_OFFSET_PERCENT_DISABLED) {
                throw new IllegalArgumentException();
            }
            mWindowAlignmentOffsetPercent = percent;
        }

        final public float getWindowAlignmentOffsetPercent() {
            return mWindowAlignmentOffsetPercent;
        }

        final public int getScrollCenter() {
            return (int) mScrollCenter;
        }

        /** set minEdge,  Integer.MIN_VALUE means unknown*/
        final public void setMinEdge(int minEdge) {
            mMinEdge = minEdge;
        }

        final public int getMinEdge() {
            return mMinEdge;
        }

        /** set minScroll,  Integer.MIN_VALUE means unknown*/
        final public void setMinScroll(int minScroll) {
            mMinScroll = minScroll;
        }

        final public int getMinScroll() {
            return mMinScroll;
        }

        final public void invalidateScrollMin() {
            mMinEdge = Integer.MIN_VALUE;
            mMinScroll = Integer.MIN_VALUE;
        }

        /** update max edge,  Integer.MAX_VALUE means unknown*/
        final public void setMaxEdge(int maxEdge) {
            mMaxEdge = maxEdge;
        }

        final public int getMaxEdge() {
            return mMaxEdge;
        }

        /** update max scroll,  Integer.MAX_VALUE means unknown*/
        final public void setMaxScroll(int maxScroll) {
            mMaxScroll = maxScroll;
        }

        final public int getMaxScroll() {
            return mMaxScroll;
        }

        final public void invalidateScrollMax() {
            mMaxEdge = Integer.MAX_VALUE;
            mMaxScroll = Integer.MAX_VALUE;
        }

        final public float updateScrollCenter(float scrollTarget) {
            mScrollCenter = scrollTarget;
            return scrollTarget;
        }

        private void reset() {
            mScrollCenter = Integer.MIN_VALUE;
            mMinEdge = Integer.MIN_VALUE;
            mMaxEdge = Integer.MAX_VALUE;
        }

        final public boolean isMinUnknown() {
            return mMinEdge == Integer.MIN_VALUE;
        }

        final public boolean isMaxUnknown() {
            return mMaxEdge == Integer.MAX_VALUE;
        }

        final public void setSize(int size) {
            mSize = size;
        }

        final public int getSize() {
            return mSize;
        }

        final public void setPadding(int paddingLow, int paddingHigh) {
            mPaddingLow = paddingLow;
            mPaddingHigh = paddingHigh;
        }

        final public int getPaddingLow() {
            return mPaddingLow;
        }

        final public int getPaddingHigh() {
            return mPaddingHigh;
        }

        final public int getClientSize() {
            return mSize - mPaddingLow - mPaddingHigh;
        }

        final public int getSystemScrollPos(boolean isAtMin, boolean isAtMax) {
            return getSystemScrollPos((int) mScrollCenter, isAtMin, isAtMax);
        }

        final public int getSystemScrollPos(int scrollCenter, boolean isAtMin, boolean isAtMax) {
            int middlePosition;
            if (!mReversedFlow) {
                if (mWindowAlignmentOffset >= 0) {
                    middlePosition = mWindowAlignmentOffset - mPaddingLow;
                } else {
                    middlePosition = mSize + mWindowAlignmentOffset - mPaddingLow;
                }
                if (mWindowAlignmentOffsetPercent != WINDOW_ALIGN_OFFSET_PERCENT_DISABLED) {
                    middlePosition += (int) (mSize * mWindowAlignmentOffsetPercent / 100);
                }
            } else {
                if (mWindowAlignmentOffset >= 0) {
                    middlePosition = mSize - mWindowAlignmentOffset - mPaddingLow;
                } else {
                    middlePosition = - mWindowAlignmentOffset - mPaddingLow;
                }
                if (mWindowAlignmentOffsetPercent != WINDOW_ALIGN_OFFSET_PERCENT_DISABLED) {
                    middlePosition -= (int) (mSize * mWindowAlignmentOffsetPercent / 100);
                }
            }
            int clientSize = getClientSize();
            int afterMiddlePosition = clientSize - middlePosition;
            boolean isMinUnknown = isMinUnknown();
            boolean isMaxUnknown = isMaxUnknown();
            if (!isMinUnknown && !isMaxUnknown &&
                    (mWindowAlignment & WINDOW_ALIGN_BOTH_EDGE) == WINDOW_ALIGN_BOTH_EDGE) {
                if (mMaxEdge - mMinEdge <= clientSize) {
                    // total children size is less than view port and we want to align
                    // both edge:  align first child to start edge of view port
                    return mReversedFlow ? mMaxEdge - mPaddingLow - clientSize
                            : mMinEdge - mPaddingLow;
                }
            }
            if (!isMinUnknown) {
                if ((!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0
                     : (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0)
                        && (isAtMin || scrollCenter - mMinEdge <= middlePosition)) {
                    // scroll center is within half of view port size: align the start edge
                    // of first child to the start edge of view port
                    return mMinEdge - mPaddingLow;
                }
            }
            if (!isMaxUnknown) {
                if ((!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0
                        : (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0)
                        && (isAtMax || mMaxEdge - scrollCenter <= afterMiddlePosition)) {
                    // scroll center is very close to the end edge of view port : align the
                    // end edge of last children (plus expanded size) to view port's end
                    return mMaxEdge - mPaddingLow - clientSize;
                }
            }
            // else put scroll center in middle of view port
            return scrollCenter - middlePosition - mPaddingLow;
        }

        final public void setReversedFlow(boolean reversedFlow) {
            mReversedFlow = reversedFlow;
        }

        @Override
        public String toString() {
            return "center: " + mScrollCenter + " min:" + mMinEdge +
                    " max:" + mMaxEdge;
        }

    }

    private int mOrientation = HORIZONTAL;

    final public Axis vertical = new Axis("vertical");

    final public Axis horizontal = new Axis("horizontal");

    private Axis mMainAxis = horizontal;

    private Axis mSecondAxis = vertical;

    final public Axis mainAxis() {
        return mMainAxis;
    }

    final public Axis secondAxis() {
        return mSecondAxis;
    }

    final public void setOrientation(int orientation) {
        mOrientation = orientation;
        if (mOrientation == HORIZONTAL) {
            mMainAxis = horizontal;
            mSecondAxis = vertical;
        } else {
            mMainAxis = vertical;
            mSecondAxis = horizontal;
        }
    }

    final public int getOrientation() {
        return mOrientation;
    }

    final public void reset() {
        mainAxis().reset();
    }

    @Override
    public String toString() {
        return new StringBuffer().append("horizontal=")
                .append(horizontal.toString())
                .append("; vertical=")
                .append(vertical.toString())
                .toString();
    }

}
