/*
 * 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.layout.LayoutConstants.ATTR_LAYOUT_MARGIN_BOTTOM;
import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_MARGIN_LEFT;
import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_MARGIN_RIGHT;
import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_MARGIN_TOP;
import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_PREFIX;
import static com.android.ide.common.layout.LayoutConstants.ID_PREFIX;
import static com.android.ide.common.layout.LayoutConstants.NEW_ID_PREFIX;

import com.android.ide.common.api.DrawingStyle;
import com.android.ide.common.api.DropFeedback;
import com.android.ide.common.api.IFeedbackPainter;
import com.android.ide.common.api.IGraphics;
import com.android.ide.common.api.INode;
import com.android.ide.common.api.Point;
import com.android.ide.common.api.Rect;
import com.android.ide.common.api.SegmentType;
import com.android.ide.common.layout.relative.DependencyGraph.Constraint;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * The {@link GuidelinePainter} is responsible for painting guidelines during an operation
 * which uses a {@link GuidelineHandler} such as a resize operation.
 */
public final class GuidelinePainter implements IFeedbackPainter {
    // ---- Implements IFeedbackPainter ----
    public void paint(IGraphics gc, INode node, DropFeedback feedback) {
        GuidelineHandler state = (GuidelineHandler) feedback.userData;

        for (INode dragged : state.mDraggedNodes) {
            gc.useStyle(DrawingStyle.DRAGGED);
            Rect bounds = dragged.getBounds();
            if (bounds.isValid()) {
                gc.fillRect(bounds);
            }
        }

        Set<INode> horizontalDeps = state.mHorizontalDeps;
        Set<INode> verticalDeps = state.mVerticalDeps;
        Set<INode> deps = new HashSet<INode>(horizontalDeps.size() + verticalDeps.size());
        deps.addAll(horizontalDeps);
        deps.addAll(verticalDeps);
        if (deps.size() > 0) {
            gc.useStyle(DrawingStyle.DEPENDENCY);
            for (INode n : deps) {
                // Don't highlight the selected nodes themselves
                if (state.mDraggedNodes.contains(n)) {
                    continue;
                }
                Rect bounds = n.getBounds();
                gc.fillRect(bounds);
            }
        }

        if (state.mBounds != null) {
            if (state instanceof MoveHandler) {
                gc.useStyle(DrawingStyle.DROP_PREVIEW);
            } else {
                // Resizing
                if (state.haveSuggestions()) {
                    gc.useStyle(DrawingStyle.RESIZE_PREVIEW);
                } else {
                    gc.useStyle(DrawingStyle.RESIZE_FAIL);
                }
            }
            gc.drawRect(state.mBounds);

            // Draw baseline preview too
            if (feedback.dragBaseline != -1) {
                int y = state.mBounds.y + feedback.dragBaseline;
                gc.drawLine(state.mBounds.x, y, state.mBounds.x2(), y);
            }
        }

        List<String> strings = new ArrayList<String>();

        showMatch(gc, state.mCurrentLeftMatch, state, strings,
                state.mLeftMargin, ATTR_LAYOUT_MARGIN_LEFT);
        showMatch(gc, state.mCurrentRightMatch, state, strings,
                state.mRightMargin, ATTR_LAYOUT_MARGIN_RIGHT);
        showMatch(gc, state.mCurrentTopMatch, state, strings,
                state.mTopMargin, ATTR_LAYOUT_MARGIN_TOP);
        showMatch(gc, state.mCurrentBottomMatch, state, strings,
                state.mBottomMargin, ATTR_LAYOUT_MARGIN_BOTTOM);

        if (strings.size() > 0) {
            // Update the drag tooltip
            StringBuilder sb = new StringBuilder(200);
            for (String s : strings) {
                if (sb.length() > 0) {
                    sb.append('\n');
                }
                sb.append(s);
            }
            feedback.tooltip = sb.toString();

            // Set the tooltip orientation to ensure that it does not interfere with
            // the constraint arrows
            if (state.mCurrentLeftMatch != null) {
                feedback.tooltipX = SegmentType.RIGHT;
            } else if (state.mCurrentRightMatch != null) {
                feedback.tooltipX = SegmentType.LEFT;
            }
            if (state.mCurrentTopMatch != null) {
                feedback.tooltipY = SegmentType.BOTTOM;
            } else if (state.mCurrentBottomMatch != null) {
                feedback.tooltipY = SegmentType.TOP;
            }
        } else {
            feedback.tooltip = null;
        }

        if (state.mHorizontalCycle != null) {
            paintCycle(gc, state, state.mHorizontalCycle);
        }
        if (state.mVerticalCycle != null) {
            paintCycle(gc, state, state.mVerticalCycle);
        }
    }

    /** Paints a particular match constraint */
    private void showMatch(IGraphics gc, Match m, GuidelineHandler state, List<String> strings,
            int margin, String marginAttribute) {
        if (m == null) {
            return;
        }
        ConstraintPainter.paintConstraint(gc, state.mBounds, m);

        // Display the constraint. Remove the @id/ and @+id/ prefixes to make the text
        // shorter and easier to read. This doesn't use stripPrefix() because the id is
        // usually not a prefix of the value (for example, 'layout_alignBottom=@+id/foo').
        String constraint = m.getConstraint(false /* generateId */);
        String description = constraint.replace(NEW_ID_PREFIX, "").replace(ID_PREFIX, "");
        if (description.startsWith(ATTR_LAYOUT_PREFIX)) {
            description = description.substring(ATTR_LAYOUT_PREFIX.length());
        }
        if (margin > 0) {
            description = String.format("%1$s, margin=%2$d dp", description, margin);
        }
        strings.add(description);
    }

    /** Paints a constraint cycle */
    void paintCycle(IGraphics gc, GuidelineHandler state, List<Constraint> cycle) {
        gc.useStyle(DrawingStyle.CYCLE);
        assert cycle.size() > 0;

        INode from = cycle.get(0).from.node;
        Rect fromBounds = from.getBounds();
        if (state.mDraggedNodes.contains(from)) {
            fromBounds = state.mBounds;
        }
        Point fromCenter = fromBounds.center();
        INode to = null;

        List<Point> points = new ArrayList<Point>();
        points.add(fromCenter);

        for (Constraint constraint : cycle) {
            assert constraint.from.node == from;
            to = constraint.to.node;
            assert from != null && to != null;

            Point toCenter = to.getBounds().center();
            points.add(toCenter);

            // Also go through the dragged node bounds
            boolean isDragged = state.mDraggedNodes.contains(to);
            if (isDragged) {
                toCenter = state.mBounds.center();
                points.add(toCenter);
            }

            from = to;
            fromCenter = toCenter;
        }

        points.add(fromCenter);
        points.add(points.get(0));

        for (int i = 1, n = points.size(); i < n; i++) {
            gc.drawLine(points.get(i-1), points.get(i));
        }
    }
}