/*
 * 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.grid;

import static com.android.ide.common.layout.GridLayoutRule.GRID_SIZE;
import static com.android.ide.common.layout.GridLayoutRule.MARGIN_SIZE;
import static com.android.ide.common.layout.grid.GridModel.UNDEFINED;

import com.android.ide.common.api.DrawingStyle;
import com.android.ide.common.api.DropFeedback;
import com.android.ide.common.api.IDragElement;
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.Rect;
import com.android.ide.common.api.SegmentType;
import com.android.ide.common.layout.GridLayoutRule;
import com.android.util.Pair;

/**
 * Painter which paints feedback during drag, drop and resizing operations, as well as
 * static selection feedback
 */
public class GridLayoutPainter {

    /**
     * Creates a painter for drop feedback
     *
     * @param rule the corresponding {@link GridLayoutRule}
     * @param elements the dragged elements
     * @return a {@link IFeedbackPainter} which can paint the drop feedback
     */
    public static IFeedbackPainter createDropFeedbackPainter(GridLayoutRule rule,
            IDragElement[] elements) {
        return new DropFeedbackPainter(rule, elements);
    }

    /**
     * Paints the structure (the grid model) of the given GridLayout.
     *
     * @param style the drawing style to use to paint the structure lines
     * @param layout the grid layout node
     * @param gc the graphics context to paint into
     * @param grid the grid model to be visualized
     */
    public static void paintStructure(DrawingStyle style, INode layout, IGraphics gc,
            GridModel grid) {
        Rect b = layout.getBounds();

        gc.useStyle(style);
        for (int row = 0; row < grid.actualRowCount; row++) {
            int y = grid.getRowY(row);
            gc.drawLine(b.x, y, b.x2(), y);
        }
        for (int column = 0; column < grid.actualColumnCount; column++) {
            int x = grid.getColumnX(column);
            gc.drawLine(x, b.y, x, b.y2());
        }
    }

    /**
     * Paints a regular grid according to the {@link GridLayoutRule#GRID_SIZE} and
     * {@link GridLayoutRule#MARGIN_SIZE} dimensions. These are the same lines that
     * snap-to-grid will align with.
     *
     * @param layout the GridLayout node
     * @param gc the graphics context to paint the grid into
     */
    public static void paintGrid(INode layout, IGraphics gc) {
        Rect b = layout.getBounds();

        int oldAlpha = gc.getAlpha();
        gc.useStyle(DrawingStyle.GUIDELINE);
        gc.setAlpha(128);

        int y1 = b.y + MARGIN_SIZE;
        int y2 = b.y2() - MARGIN_SIZE;
        for (int y = y1; y < y2; y += GRID_SIZE) {
            int x1 = b.x + MARGIN_SIZE;
            int x2 = b.x2() - MARGIN_SIZE;
            for (int x = x1; x < x2; x += GRID_SIZE) {
                gc.drawPoint(x, y);
            }
        }
        gc.setAlpha(oldAlpha);
    }

    /**
     * Paint resizing feedback (which currently paints the grid model faintly.)
     *
     * @param gc the graphics context
     * @param layout the GridLayout
     * @param grid the grid model
     */
    public static void paintResizeFeedback(IGraphics gc, INode layout, GridModel grid) {
        paintStructure(DrawingStyle.GRID, layout, gc, grid);
    }

    /**
     * A painter which can paint the drop feedback for elements being dragged into or
     * within a GridLayout.
     */
    private static class DropFeedbackPainter implements IFeedbackPainter {
        private final GridLayoutRule mRule;
        private final IDragElement[] mElements;

        /** Constructs a new {@link GridLayoutPainter} bound to the given {@link GridLayoutRule}
         * @param rule the corresponding rule
         * @param elements the elements to draw */
        public DropFeedbackPainter(GridLayoutRule rule, IDragElement[] elements) {
            mRule = rule;
            mElements = elements;
        }

        // Implements IFeedbackPainter
        public void paint(IGraphics gc, INode node, DropFeedback feedback) {
            Rect b = node.getBounds();
            if (!b.isValid()) {
                return;
            }

            // Highlight the receiver
            gc.useStyle(DrawingStyle.DROP_RECIPIENT);
            gc.drawRect(b);
            GridDropHandler data = (GridDropHandler) feedback.userData;

            if (!GridLayoutRule.sGridMode) {
                paintFreeFormDropFeedback(gc, node, feedback, b, data);
            } else {
                paintGridModeDropFeedback(gc, b, data);
            }
        }

        /**
         * Paints the drag feedback for a free-form mode drag
         */
        private void paintFreeFormDropFeedback(IGraphics gc, INode node, DropFeedback feedback,
                Rect b, GridDropHandler data) {
            GridModel grid = data.getGrid();
            if (GridLayoutRule.sSnapToGrid) {
                GridLayoutPainter.paintGrid(node, gc);
            }
            GridLayoutPainter.paintStructure(DrawingStyle.GRID, node, gc, grid);

            GridMatch rowMatch = data.getRowMatch();
            GridMatch columnMatch = data.getColumnMatch();

            if (rowMatch == null || columnMatch == null) {
                return;
            }

            IDragElement first = mElements[0];
            Rect dragBounds = first.getBounds();
            int offsetX = 0;
            int offsetY = 0;
            if (rowMatch.type == SegmentType.BOTTOM) {
                offsetY -= dragBounds.h;
            } else if (rowMatch.type == SegmentType.BASELINE) {
                offsetY -= feedback.dragBaseline;
            }
            if (columnMatch.type == SegmentType.RIGHT) {
                offsetX -= dragBounds.w;
            } else if (columnMatch.type == SegmentType.CENTER_HORIZONTAL) {
                offsetX -= dragBounds.w / 2;
            }

            // Draw guidelines for matches
            int y = rowMatch.matchedLine;
            int x = columnMatch.matchedLine;
            Rect bounds = first.getBounds();

            // Draw margin
            if (rowMatch.margin != UNDEFINED && rowMatch.margin > 0) {
                gc.useStyle(DrawingStyle.DISTANCE);
                int centerX = bounds.w / 2 + offsetX + x;
                int y1;
                int y2;
                if (rowMatch.type == SegmentType.TOP) {
                    y1 = offsetY + y - 1;
                    y2 = rowMatch.matchedLine - rowMatch.margin;
                } else {
                    assert rowMatch.type == SegmentType.BOTTOM;
                    y1 = bounds.h + offsetY + y - 1;
                    y2 = rowMatch.matchedLine + rowMatch.margin;
                }
                gc.drawLine(b.x, y1, b.x2(), y1);
                gc.drawLine(b.x, y2, b.x2(), y2);
                gc.drawString(Integer.toString(rowMatch.margin),
                        centerX - 3, y1 + (y2 - y1 - 16) / 2);
            } else {
                gc.useStyle(rowMatch.margin == 0 ? DrawingStyle.DISTANCE
                        : rowMatch.createCell ? DrawingStyle.GUIDELINE_DASHED
                                : DrawingStyle.GUIDELINE);
                gc.drawLine(b.x, y, b.x2(), y );
            }

            if (columnMatch.margin != UNDEFINED && columnMatch.margin > 0) {
                gc.useStyle(DrawingStyle.DISTANCE);
                int centerY = bounds.h / 2 + offsetY + y;
                int x1;
                int x2;
                if (columnMatch.type == SegmentType.LEFT) {
                    x1 = offsetX + x - 1;
                    x2 = columnMatch.matchedLine - columnMatch.margin;
                } else {
                    assert columnMatch.type == SegmentType.RIGHT;
                    x1 = bounds.w + offsetX + x - 1;
                    x2 = columnMatch.matchedLine + columnMatch.margin;
                }
                gc.drawLine(x1, b.y, x1, b.y2());
                gc.drawLine(x2, b.y, x2, b.y2());
                gc.drawString(Integer.toString(columnMatch.margin),
                        x1 + (x2 - x1 - 16) / 2, centerY - 3);
            } else {
                gc.useStyle(columnMatch.margin == 0 ? DrawingStyle.DISTANCE
                        : columnMatch.createCell ? DrawingStyle.GUIDELINE_DASHED
                                : DrawingStyle.GUIDELINE);
                gc.drawLine(x, b.y, x, b.y2());
            }

            // Draw preview rectangle of the first dragged element
            gc.useStyle(DrawingStyle.DROP_PREVIEW);
            mRule.drawElement(gc, first, x + offsetX - bounds.x, y + offsetY - bounds.y);

            // Preview baseline as well
            if (feedback.dragBaseline != -1) {
                int x1 = dragBounds.x + x + offsetX - bounds.x;
                int y1 = dragBounds.y + y + offsetY - bounds.y + feedback.dragBaseline;
                gc.drawLine(x1, y1, x1 + dragBounds.w, y1);
            }
        }

        /**
         * Paints the drag feedback for a grid-mode drag
         */
        private void paintGridModeDropFeedback(IGraphics gc, Rect b, GridDropHandler data) {
            int radius = mRule.getNewCellSize();
            GridModel grid = data.getGrid();

            gc.useStyle(DrawingStyle.GUIDELINE);
            // Paint grid
            for (int row = 1; row < grid.actualRowCount; row++) {
                int y = grid.getRowY(row);
                gc.drawLine(b.x, y - radius, b.x2(), y - radius);
                gc.drawLine(b.x, y + radius, b.x2(), y + radius);

            }
            for (int column = 1; column < grid.actualColumnCount; column++) {
                int x = grid.getColumnX(column);
                gc.drawLine(x - radius, b.y, x - radius, b.y2());
                gc.drawLine(x + radius, b.y, x + radius, b.y2());
            }
            gc.drawRect(b.x, b.y, b.x2(), b.y2());
            gc.drawRect(b.x + 2 * radius, b.y + 2 * radius,
                    b.x2() - 2 * radius, b.y2() - 2 * radius);

            int column = data.getColumnMatch().cellIndex;
            int row = data.getRowMatch().cellIndex;
            boolean createColumn = data.getColumnMatch().createCell;
            boolean createRow = data.getRowMatch().createCell;

            Rect cellBounds = grid.getCellBounds(row, column, 1, 1);

            IDragElement first = mElements[0];
            Rect dragBounds = first.getBounds();
            int offsetX = cellBounds.x - dragBounds.x;
            int offsetY = cellBounds.y - dragBounds.y;

            gc.useStyle(DrawingStyle.DROP_ZONE_ACTIVE);
            if (createColumn) {
                gc.fillRect(new Rect(cellBounds.x - radius,
                        cellBounds.y + (createRow ? -radius : radius),
                        2 * radius + 1, cellBounds.h - (createRow ? 0 : 2 * radius)));
                offsetX -= radius + dragBounds.w / 2;
            }
            if (createRow) {
                gc.fillRect(new Rect(cellBounds.x + radius, cellBounds.y - radius,
                        cellBounds.w - 2 * radius, 2 * radius + 1));
                offsetY -= radius + dragBounds.h / 2;
            } else if (!createColumn) {
                // Choose this cell
                gc.fillRect(new Rect(cellBounds.x + radius, cellBounds.y + radius,
                        cellBounds.w - 2 * radius, cellBounds.h - 2 * radius));
            }

            gc.useStyle(DrawingStyle.DROP_PREVIEW);
            mRule.drawElement(gc, first, offsetX, offsetY);
        }
    }

    /**
     * Paints the structure (the row and column boundaries) of the given
     * GridLayout
     *
     * @param view the instance of the GridLayout whose structure should be
     *            painted
     * @param style the drawing style to use for the cell boundaries
     * @param layout the layout element
     * @param gc the graphics context
     * @return true if the structure was successfully inferred from the view and
     *         painted
     */
    public static boolean paintStructure(Object view, DrawingStyle style, INode layout,
            IGraphics gc) {
        Pair<int[],int[]> cellBounds = GridModel.getAxisBounds(view);
        if (cellBounds != null) {
            int[] xs = cellBounds.getFirst();
            int[] ys = cellBounds.getSecond();
            Rect b = layout.getBounds();
            gc.useStyle(style);
            for (int row = 0; row < ys.length; row++) {
                int y = ys[row] + b.y;
                gc.drawLine(b.x, y, b.x2(), y);
            }
            for (int column = 0; column < xs.length; column++) {
                int x = xs[column] + b.x;
                gc.drawLine(x, b.y, x, b.y2());
            }

            return true;
        } else {
            return false;
        }
    }
}
