/*
 * Clover - 4chan browser https://github.com/Floens/Clover/
 * Copyright (C) 2014  Floens
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.floens.chan.ui.drawable;

import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;

import org.floens.chan.ui.helper.PinHelper;

import static org.floens.chan.utils.AndroidUtils.dp;

public class ArrowMenuDrawable extends Drawable {
    private final Paint mPaint = new Paint();

    // The angle in degress that the arrow head is inclined at.
    private static final float ARROW_HEAD_ANGLE = (float) Math.toRadians(45);
    private final float mBarThickness;
    // The length of top and bottom bars when they merge into an arrow
    private final float mTopBottomArrowSize;
    // The length of middle bar
    private final float mBarSize;
    // The length of the middle bar when arrow is shaped
    private final float mMiddleArrowSize;
    // The space between bars when they are parallel
    private final float mBarGap;
    // Use Path instead of canvas operations so that if color has transparency, overlapping sections
    // wont look different
    private final Path mPath = new Path();
    // The reported intrinsic size of the drawable.
    private final int mSize;
    // Whether we should mirror animation when animation is reversed.
    private boolean mVerticalMirror = false;
    // The interpolated version of the original progress
    private float mProgress;

    private String badgeText;
    private boolean badgeRed = false;
    private Paint badgePaint = new Paint();
    private Rect badgeTextBounds = new Rect();

    public ArrowMenuDrawable() {
        mPaint.setColor(0xffffffff);
        mPaint.setAntiAlias(true);
        mSize = dp(24f);
        mBarSize = dp(18f);
        mTopBottomArrowSize = dp(11.31f);
        mBarThickness = dp(2f);
        mBarGap = dp(3f);
        mMiddleArrowSize = dp(16f);

        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeJoin(Paint.Join.ROUND);
        mPaint.setStrokeCap(Paint.Cap.SQUARE);
        mPaint.setStrokeWidth(mBarThickness);

        setProgress(0f);

        badgePaint.setAntiAlias(true);
    }

    @Override
    public void draw(Canvas canvas) {
        Rect bounds = getBounds();
        // Interpolated widths of arrow bars
        final float arrowSize = lerp(mBarSize, mTopBottomArrowSize, mProgress);
        final float middleBarSize = lerp(mBarSize, mMiddleArrowSize, mProgress);
        // Interpolated size of middle bar
        final float middleBarCut = lerp(0, mBarThickness / 2, mProgress);
        // The rotation of the top and bottom bars (that make the arrow head)
        final float rotation = lerp(0, ARROW_HEAD_ANGLE, mProgress);

        // The whole canvas rotates as the transition happens
        final float canvasRotate = lerp(-180, 0, mProgress);
        final float topBottomBarOffset = lerp(mBarGap + mBarThickness, 0, mProgress);
        mPath.rewind();

        final float arrowEdge = -middleBarSize / 2;
        // draw middle bar
        mPath.moveTo(arrowEdge + middleBarCut, 0);
        mPath.rLineTo(middleBarSize - middleBarCut, 0);

        float arrowWidth = arrowSize * (float) Math.cos(rotation);
        float arrowHeight = arrowSize * (float) Math.sin(rotation);

        if (mProgress == 0f || mProgress == 1f) {
            arrowWidth = Math.round(arrowWidth);
            arrowHeight = Math.round(arrowHeight);
        }

        // top bar
        mPath.moveTo(arrowEdge, topBottomBarOffset);
        mPath.rLineTo(arrowWidth, arrowHeight);

        // bottom bar
        mPath.moveTo(arrowEdge, -topBottomBarOffset);
        mPath.rLineTo(arrowWidth, -arrowHeight);
        mPath.moveTo(0, 0);
        mPath.close();

        canvas.save();
        // Rotate the whole canvas if spinning.
        canvas.rotate(canvasRotate * ((mVerticalMirror) ? -1 : 1),
                bounds.centerX(), bounds.centerY());
        canvas.translate(bounds.centerX(), bounds.centerY());
        canvas.drawPath(mPath, mPaint);

        canvas.restore();

        // Draw a badge over the arrow/menu
        if (badgeText != null) {
            canvas.save();
            float badgeSize = mSize * 0.7f;
            float badgeX = mSize - badgeSize / 2f;
            float badgeY = badgeSize / 2f;

            if (badgeRed) {
                badgePaint.setColor(0xddf44336);
            } else {
                badgePaint.setColor(0x89000000);
            }

            canvas.drawCircle(badgeX, badgeY, badgeSize / 2f, badgePaint);

            float textSize;
            if (badgeText.length() == 1) {
                textSize = badgeSize * 0.7f;
            } else if (badgeText.length() == 2) {
                textSize = badgeSize * 0.6f;
            } else {
                textSize = badgeSize * 0.5f;
            }

            badgePaint.setColor(0xffffffff);
            badgePaint.setTextSize(textSize);
            badgePaint.getTextBounds(badgeText, 0, badgeText.length(), badgeTextBounds);
            canvas.drawText(badgeText, badgeX - badgeTextBounds.right / 2f, badgeY - badgeTextBounds.top / 2f, badgePaint);
            canvas.restore();
        }
    }

    @Override
    public void setAlpha(int i) {
        mPaint.setAlpha(i);
    }

    @Override
    public void setColorFilter(ColorFilter colorFilter) {
        mPaint.setColorFilter(colorFilter);
    }

    @Override
    public int getIntrinsicHeight() {
        return mSize;
    }

    @Override
    public int getIntrinsicWidth() {
        return mSize;
    }

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSLUCENT;
    }

    public float getProgress() {
        return mProgress;
    }

    public void setProgress(float progress) {
        if (progress != mProgress) {
            if (progress == 1f) {
                mVerticalMirror = true;
            } else if (progress == 0f) {
                mVerticalMirror = false;
            }
            mProgress = progress;
            invalidateSelf();
        }
    }

    public void setBadge(int count, boolean red) {
        String text = count == 0 ? null : (PinHelper.getShortUnreadCount(count));
        if (badgeRed != red || !TextUtils.equals(text, badgeText)) {
            badgeText = text;
            badgeRed = red;
            invalidateSelf();
        }
    }

    /**
     * Linear interpolate between a and b with parameter t.
     */
    private static float lerp(float a, float b, float t) {
        return a + (b - a) * t;
    }
}
