/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
 *
 * 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 com.android.ide.common.layout.relative;

import static com.android.ide.common.api.MarginType.NO_MARGIN;
import static com.android.ide.common.api.SegmentType.BASELINE;
import static com.android.ide.common.api.SegmentType.BOTTOM;
import static com.android.ide.common.api.SegmentType.CENTER_HORIZONTAL;
import static com.android.ide.common.api.SegmentType.CENTER_VERTICAL;
import static com.android.ide.common.api.SegmentType.LEFT;
import static com.android.ide.common.api.SegmentType.RIGHT;
import static com.android.ide.common.api.SegmentType.TOP;
import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI;
import static com.android.ide.common.layout.LayoutConstants.ATTR_ID;
import static java.lang.Math.abs;

import com.android.ide.common.api.DropFeedback;
import com.android.ide.common.api.IClientRulesEngine;
import com.android.ide.common.api.INode;
import com.android.ide.common.api.Rect;
import com.android.ide.common.api.Segment;
import com.android.ide.common.api.SegmentType;
import com.android.ide.common.layout.BaseLayoutRule;

import java.util.Collections;
import java.util.Set;

/**
 * A {@link ResizeHandler} is a {@link GuidelineHandler} which handles resizing of individual
 * edges in a RelativeLayout.
 */
public class ResizeHandler extends GuidelineHandler {
    public final INode mResized;
    public final SegmentType mHorizontalEdgeType;
    public final SegmentType mVerticalEdgeType;

    public ResizeHandler(INode layout, INode resized,
            IClientRulesEngine rulesEngine,
            SegmentType horizontalEdgeType, SegmentType verticalEdgeType) {
        super(layout, rulesEngine);

        assert horizontalEdgeType != null || verticalEdgeType != null;
        assert horizontalEdgeType != BASELINE && verticalEdgeType != BASELINE;
        assert horizontalEdgeType != CENTER_HORIZONTAL && verticalEdgeType != CENTER_HORIZONTAL;
        assert horizontalEdgeType != CENTER_VERTICAL && verticalEdgeType != CENTER_VERTICAL;

        mResized = resized;
        mHorizontalEdgeType = horizontalEdgeType;
        mVerticalEdgeType = verticalEdgeType;

        Set<INode> nodes = Collections.singleton(resized);
        mDraggedNodes = nodes;

        mHorizontalDeps = mDependencyGraph.dependsOn(nodes, false /* vertical */);
        mVerticalDeps = mDependencyGraph.dependsOn(nodes, true /* vertical */);

        if (horizontalEdgeType != null) {
            if (horizontalEdgeType == TOP) {
                mMoveTop = true;
            } else if (horizontalEdgeType == BOTTOM) {
                mMoveBottom = true;
            }
        }
        if (verticalEdgeType != null) {
            if (verticalEdgeType == LEFT) {
                mMoveLeft = true;
            } else if (verticalEdgeType == RIGHT) {
                mMoveRight = true;
            }
        }

        for (INode child : layout.getChildren()) {
            if (child != resized) {
                String id = child.getStringAttr(ANDROID_URI, ATTR_ID);
                addBounds(child, id,
                        !mHorizontalDeps.contains(child),
                        !mVerticalDeps.contains(child));
            }
        }

        addBounds(layout, layout.getStringAttr(ANDROID_URI, ATTR_ID), true, true);
    }

    @Override
    protected void snapVertical(Segment vEdge, int x, Rect newBounds) {
        int maxDistance = BaseLayoutRule.getMaxMatchDistance();
        if (vEdge.edgeType == LEFT) {
            int margin = mSnap ? 0 : abs(newBounds.x - x);
            if (margin > maxDistance) {
                mLeftMargin = margin;
            } else {
                newBounds.w += newBounds.x - x;
                newBounds.x = x;
            }
        } else if (vEdge.edgeType == RIGHT) {
            int margin = mSnap ? 0 : abs(newBounds.x - (x - newBounds.w));
            if (margin > maxDistance) {
                mRightMargin = margin;
            } else {
                newBounds.w = x - newBounds.x;
            }
        } else {
            assert false : vEdge;
        }
    }

    @Override
    protected void snapHorizontal(Segment hEdge, int y, Rect newBounds) {
        int maxDistance = BaseLayoutRule.getMaxMatchDistance();
        if (hEdge.edgeType == TOP) {
            int margin = mSnap ? 0 : abs(newBounds.y - y);
            if (margin > maxDistance) {
                mTopMargin = margin;
            } else {
                newBounds.h += newBounds.y - y;
                newBounds.y = y;
            }
        } else if (hEdge.edgeType == BOTTOM) {
            int margin = mSnap ? 0 : abs(newBounds.y - (y - newBounds.h));
            if (margin > maxDistance) {
                mBottomMargin = margin;
            } else {
                newBounds.h = y - newBounds.y;
            }
        } else {
            assert false : hEdge;
        }
    }

    @Override
    protected boolean isEdgeTypeCompatible(SegmentType edge, SegmentType dragged, int delta) {
        boolean compatible = super.isEdgeTypeCompatible(edge, dragged, delta);

        // When resizing and not snapping (e.g. using margins to pick a specific pixel
        // width) we cannot use -negative- margins to jump back to a closer edge; we
        // must always use positive margins, so mark closer edges that result in a negative
        // margin as not compatible.
        if (compatible && !mSnap) {
            switch (dragged) {
                case LEFT:
                case TOP:
                    return delta <= 0;
                default:
                    return delta >= 0;
            }
        }

        return compatible;
    }

    public void updateResize(DropFeedback feedback, INode child, Rect newBounds,
            int modifierMask) {
        mSnap = (modifierMask & DropFeedback.MODIFIER2) == 0;
        mBounds = newBounds;
        clearSuggestions();

        Rect b = newBounds;
        Segment hEdge = null;
        Segment vEdge = null;
        String childId = child.getStringAttr(ANDROID_URI, ATTR_ID);

        // TODO: MarginType=NO_MARGIN may not be right. Consider resizing a widget
        //   that has margins and how that should be handled.

        if (mHorizontalEdgeType == TOP) {
            hEdge = new Segment(b.y, b.x, b.x2(), child, childId, mHorizontalEdgeType, NO_MARGIN);
        } else if (mHorizontalEdgeType == BOTTOM) {
            hEdge = new Segment(b.y2(), b.x, b.x2(), child, childId, mHorizontalEdgeType,
                    NO_MARGIN);
        } else {
            assert mHorizontalEdgeType == null;
        }

        if (mVerticalEdgeType == LEFT) {
            vEdge = new Segment(b.x, b.y, b.y2(), child, childId, mVerticalEdgeType, NO_MARGIN);
        } else if (mVerticalEdgeType == RIGHT) {
            vEdge = new Segment(b.x2(), b.y, b.y2(), child, childId, mVerticalEdgeType, NO_MARGIN);
        } else {
            assert mVerticalEdgeType == null;
        }

        mTopMargin = mBottomMargin = mLeftMargin = mRightMargin = 0;

        if (hEdge != null && mHorizontalEdges.size() > 0) {
            // Compute horizontal matches
            mHorizontalSuggestions = findClosest(hEdge, mHorizontalEdges);

            Match match = pickBestMatch(mHorizontalSuggestions);
            if (match != null
                    && (!mSnap || Math.abs(match.delta) < BaseLayoutRule.getMaxMatchDistance())) {
                if (mHorizontalDeps.contains(match.edge.node)) {
                    match.cycle = true;
                }

                snapHorizontal(hEdge, match.edge.at, newBounds);

                if (hEdge.edgeType == TOP) {
                    mCurrentTopMatch = match;
                } else if (hEdge.edgeType == BOTTOM) {
                    mCurrentBottomMatch = match;
                } else {
                    assert hEdge.edgeType == CENTER_HORIZONTAL
                            || hEdge.edgeType == BASELINE : hEdge;
                    mCurrentTopMatch = match;
                }
            }
        }

        if (vEdge != null && mVerticalEdges.size() > 0) {
            mVerticalSuggestions = findClosest(vEdge, mVerticalEdges);

            Match match = pickBestMatch(mVerticalSuggestions);
            if (match != null
                    && (!mSnap || Math.abs(match.delta) < BaseLayoutRule.getMaxMatchDistance())) {
                if (mVerticalDeps.contains(match.edge.node)) {
                    match.cycle = true;
                }

                // Snap
                snapVertical(vEdge, match.edge.at, newBounds);

                if (vEdge.edgeType == LEFT) {
                    mCurrentLeftMatch = match;
                } else if (vEdge.edgeType == RIGHT) {
                    mCurrentRightMatch = match;
                } else {
                    assert vEdge.edgeType == CENTER_VERTICAL;
                    mCurrentLeftMatch = match;
                }
            }
        }

        checkCycles(feedback);
    }
}