package com.mapbox.mapboxsdk.views.safecanvas;

import android.graphics.Matrix;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;

/**
 * The SafeTranslatedPath class is designed to work in conjunction with {@link
 * SafeTranslatedCanvas}
 * to work around various Android issues with large canvases. For the two classes to work together,
 * call {@link #onDrawCycleStart} at the start of the
 * {@link com.mapbox.mapboxsdk.overlay.Overlay.Overlay#drawSafe}
 * method of your
 * {@link com.mapbox.mapboxsdk.overlay.Overlay.Overlay}. This will set the adjustment needed to
 * draw
 * your Path safely on the canvas
 * without any drawing distortion at high zoom levels. Methods of the {@link Path} class that use
 * unsafe float types have been deprecated in favor of replacement methods that use doubles.
 *
 * @author Marc Kurtz
 * @see {@link ISafeCanvas}
 */
public class SafeTranslatedPath extends Path {

    private static final RectF sRectF = new RectF();

    public int xOffset = 0;
    public int yOffset = 0;

    /**
     * This method <b>must</b> be called at the start of the {@link Overlay#drawSafe} draw cycle
     * method. This will adjust the Path to the current state of the {@link ISafeCanvas} passed to
     * it.
     */
    public void onDrawCycleStart(ISafeCanvas canvas) {
        // Adjust the current position of the path
        int deltaX = canvas.getXOffset() - xOffset;
        int deltaY = canvas.getYOffset() - yOffset;
        super.offset(deltaX, deltaY);

        // Record the new offset
        xOffset = canvas.getXOffset();
        yOffset = canvas.getYOffset();
    }

    @Override
    public void reset() {
        super.reset();
    }

    @Override
    public void rewind() {
        super.rewind();
    }

    @Override
    public void set(Path src) {
        super.set(src);
    }

    @Override
    public FillType getFillType() {
        return super.getFillType();
    }

    @Override
    public void setFillType(FillType ft) {
        super.setFillType(ft);
    }

    @Override
    public boolean isInverseFillType() {
        return super.isInverseFillType();
    }

    @Override
    public void toggleInverseFillType() {
        super.toggleInverseFillType();
    }

    @Override
    public boolean isEmpty() {
        return super.isEmpty();
    }

    /**
     * @see {@link #isRect(RectF)}
     */
    public boolean isRect(Rect rect) {
        // Should we offset here?
        rect.offset(xOffset, yOffset);
        boolean result = super.isRect(this.toOffsetRectF(rect, sRectF));
        rect.offset(-xOffset, -yOffset);
        return result;
    }

    /**
     * @see {@link #computeBounds(RectF, boolean)}
     */
    public void computeBounds(Rect bounds, boolean exact) {
        super.computeBounds(sRectF, exact);
        bounds.set((int) sRectF.left, (int) sRectF.top, (int) sRectF.right, (int) sRectF.bottom);
        bounds.offset(-xOffset, -yOffset);
    }

    @Override
    public void incReserve(int extraPtCount) {
        super.incReserve(extraPtCount);
    }

    /**
     * @see {@link #moveTo(float, float)}
     */
    public void moveTo(double x, double y) {
        super.moveTo((float) (x + xOffset), (float) (y + yOffset));
    }

    @Override
    public void rMoveTo(float dx, float dy) {
        super.rMoveTo(dx, dy);
    }

    /**
     * @see {@link #lineTo(float, float)}
     */
    public void lineTo(double x, double y) {
        super.lineTo((float) (x + xOffset), (float) (y + yOffset));
    }

    @Override
    public void rLineTo(float dx, float dy) {
        super.rLineTo(dx, dy);
    }

    /**
     * @see {@link #quadTo(float, float, float, float)}
     */
    public void quadTo(double x1, double y1, double x2, double y2) {
        super.quadTo((float) (x1 + xOffset), (float) (y1 + yOffset), (float) (x2 + xOffset),
                (float) (y2 + yOffset));
    }

    @Override
    public void rQuadTo(float dx1, float dy1, float dx2, float dy2) {
        super.rQuadTo(dx1, dy1, dx2, dy2);
    }

    /**
     * @see {@link #cubicTo(float, float, float, float, float, float)}
     */
    public void cubicTo(double x1, double y1, double x2, double y2, double x3, double y3) {
        super.cubicTo((float) (x1 + xOffset), (float) (y1 + yOffset), (float) (x2 + xOffset),
                (float) (y2 + yOffset), (float) (x3 + xOffset), (float) (y3 + yOffset));
    }

    @Override
    public void rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3) {
        super.rCubicTo(x1, y1, x2, y2, x3, y3);
    }

    /**
     * @see {@link #arcTo(RectF, float, float, boolean)}
     */
    public void arcTo(Rect oval, float startAngle, float sweepAngle, boolean forceMoveTo) {
        oval.offset(xOffset, yOffset);
        super.arcTo(this.toOffsetRectF(oval, sRectF), startAngle, sweepAngle, forceMoveTo);
        oval.offset(-xOffset, -yOffset);
    }

    /**
     * @see {@link #arcTo(RectF, float, float)}
     */
    public void arcTo(Rect oval, float startAngle, float sweepAngle) {
        oval.offset(xOffset, yOffset);
        super.arcTo(this.toOffsetRectF(oval, sRectF), startAngle, sweepAngle);
        oval.offset(-xOffset, -yOffset);
    }

    @Override
    public void close() {
        super.close();
    }

    /**
     * @see {@link #addRect(RectF, Direction)}
     */
    public void addRect(Rect rect, Direction dir) {
        rect.offset(xOffset, yOffset);
        super.addRect(this.toOffsetRectF(rect, sRectF), dir);
        rect.offset(-xOffset, -yOffset);
    }

    /**
     * @see {@link #addRect(float, float, float, float, Direction)}
     */
    public void addRect(double left, double top, double right, double bottom, Direction dir) {
        super.addRect((float) (left + xOffset), (float) (top + yOffset), (float) (right + xOffset),
                (float) (bottom + yOffset), dir);
    }

    /**
     * @see {@link #addOval(RectF, Direction)
     */
    public void addOval(Rect oval, Direction dir) {
        oval.offset(xOffset, yOffset);
        super.addOval(this.toOffsetRectF(oval, sRectF), dir);
        oval.offset(-xOffset, -yOffset);
    }

    /**
     * @see {@link #addCircle(float, float, float, Direction)}
     */
    public void addCircle(double x, double y, float radius, Direction dir) {
        super.addCircle((float) (x + xOffset), (float) (y + yOffset), radius, dir);
    }

    /**
     * @see {@link #addArc(RectF, float, float)}
     */
    public void addArc(Rect oval, float startAngle, float sweepAngle) {
        oval.offset(xOffset, yOffset);
        super.addArc(this.toOffsetRectF(oval, sRectF), startAngle, sweepAngle);
        oval.offset(-xOffset, -yOffset);
    }

    /**
     * @see {@link #addRoundRect(RectF, float, float)}
     */
    public void addRoundRect(Rect rect, float rx, float ry, Direction dir) {
        rect.offset(xOffset, yOffset);
        super.addRoundRect(this.toOffsetRectF(rect, sRectF), rx, ry, dir);
        rect.offset(-xOffset, -yOffset);
    }

    /**
     * @see {@link #addRoundRect(RectF, float, Direction)}
     */
    public void addRoundRect(Rect rect, float[] radii, Direction dir) {
        rect.offset(xOffset, yOffset);
        super.addRoundRect(this.toOffsetRectF(rect, sRectF), radii, dir);
        rect.offset(-xOffset, -yOffset);
    }

    @Override
    public void addPath(Path src, float dx, float dy) {
        boolean safePath = src instanceof SafeTranslatedPath;
        if (!safePath) {
            src.offset(xOffset, yOffset);
        }
        super.addPath(src, dx, dy);
        if (!safePath) {
            src.offset(-xOffset, -yOffset);
        }
    }

    @Override
    public void addPath(Path src) {
        boolean safePath = src instanceof SafeTranslatedPath;
        if (!safePath) {
            src.offset(xOffset, yOffset);
        }
        super.addPath(src);
        if (!safePath) {
            src.offset(-xOffset, -yOffset);
        }
    }

    @Override
    public void addPath(Path src, Matrix matrix) {
        boolean safePath = src instanceof SafeTranslatedPath;
        if (!safePath) {
            matrix.preTranslate(xOffset, yOffset);
        }
        super.addPath(src, matrix);
        if (!safePath) {
            matrix.preTranslate(-xOffset, -yOffset);
        }
    }

    @Override
    public void offset(float dx, float dy, Path dst) {
        super.offset(dx, dy, dst);
    }

    @Override
    public void offset(float dx, float dy) {
        super.offset(dx, dy);
    }

    /**
     * @see {@link #setLastPoint(float, float)}
     */
    public void setLastPoint(double dx, double dy) {
        super.setLastPoint((float) (dx + xOffset), (float) (dy + yOffset));
    }

    @Override
    public void transform(Matrix matrix, Path dst) {
        // Should we offset here?
        super.transform(matrix, dst);
    }

    @Override
    public void transform(Matrix matrix) {
        // Should we offset here?
        super.transform(matrix);
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
    }

    /**
     * Helper function to convert a Rect to RectF and adjust the values of the Rect by the offsets.
     */
    protected final RectF toOffsetRectF(Rect rect, RectF reuse) {
        if (reuse == null) {
            reuse = new RectF();
        }

        reuse.set(rect.left + xOffset, rect.top + yOffset, rect.right + xOffset,
                rect.bottom + yOffset);
        return reuse;
    }
}