
/*
 * Copyright 2016 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */
#include "include/core/SkCanvas.h"
#include "include/core/SkColorFilter.h"
#include "include/core/SkPath.h"
#include "include/core/SkPathBuilder.h"
#include "include/core/SkPoint3.h"
#include "include/core/SkRRect.h"
#include "include/pathops/SkPathOps.h"
#include "include/utils/SkCamera.h"
#include "include/utils/SkShadowUtils.h"
#include "src/base/SkUTF.h"
#include "src/core/SkBlurMask.h"
#include "tools/ToolUtils.h"
#include "tools/timer/TimeUtils.h"
#include "tools/viewer/Slide.h"

////////////////////////////////////////////////////////////////////////////

class ShadowsSlide : public Slide {
    SkPath    fRectPath;
    SkPath    fRRPath;
    SkPath    fCirclePath;
    SkPath    fFunkyRRPath;
    SkPath    fCubicPath;
    SkPath    fStarPath;
    SkPath    fSquareRRectPath;
    SkPath    fWideRectPath;
    SkPath    fWideOvalPath;
    SkPath    fNotchPath;
    SkPath    fTabPath;

    SkPoint3  fLightPos;
    SkScalar  fZDelta = 0;
    SkScalar  fAnimTranslate = 0;
    SkScalar  fAnimAngle = 0;
    SkScalar  fAnimAlpha = 1;

    bool      fShowAmbient = true;
    bool      fShowSpot = true;
    bool      fUseAlt = false;
    bool      fUseBlur = true;
    bool      fShowObject = true;
    bool      fIgnoreShadowAlpha = false;
    bool      fDoAlphaAnimation = false;

public:
    ShadowsSlide() { fName = "AndroidShadows"; }

    void load(SkScalar w, SkScalar h) override {
        fCirclePath = SkPath::Circle(0, 0, 50);
        fRectPath = SkPath::Rect(SkRect::MakeXYWH(-100, -50, 200, 100));
        fRRPath = SkPath::RRect(SkRRect::MakeRectXY(SkRect::MakeXYWH(-100, -50, 200, 100), 4, 4));
        fFunkyRRPath = SkPath::RRect(SkRect::MakeXYWH(-50, -50, SK_Scalar1 * 100, SK_Scalar1 * 100),
                                    40 * SK_Scalar1, 20 * SK_Scalar1,
                                    SkPathDirection::kCW);
        fCubicPath = SkPathBuilder()
                     .cubicTo(100, 50, 20, 100, 0, 0)
                     .detach();
        fStarPath = SkPathBuilder()
                    .moveTo(0.0f, -50.0f)
                    .lineTo(14.43f, -25.0f)
                    .lineTo(43.30f, -25.0f)
                    .lineTo(28.86f, 0.0f)
                    .lineTo(43.30f, 25.0f)
                    .lineTo(14.43f, 25.0f)
                    .lineTo(0.0f, 50.0f)
                    .lineTo(-14.43f, 25.0f)
                    .lineTo(-43.30f, 25.0f)
                    .lineTo(-28.86f, 0.0f)
                    .lineTo(-43.30f, -25.0f)
                    .lineTo(-14.43f, -25.0f)
                    .detach();
        fSquareRRectPath = SkPath::RRect(SkRRect::MakeRectXY(SkRect::MakeXYWH(-50, -50, 100, 100),
                                                      10, 10));
        fWideRectPath = SkPath::Rect(SkRect::MakeXYWH(0, 0, 630, 70));
        fWideOvalPath = SkPath::Oval(SkRect::MakeXYWH(0, 0, 630, 70));

        fNotchPath = SkPathBuilder()
                     .moveTo(0, 80)
                     .arcTo(SkRect::MakeLTRB(-20, 80, 20, 120), -90, -90, false)
                     .lineTo(-75, 100)
                     .lineTo(-75, -100)
                     .lineTo(75, -100)
                     .lineTo(75, 100)
                     .arcTo(SkRect::MakeLTRB(-20, 80, 20, 120), 0, -90, false)
                     .detach();

        fTabPath = SkPathBuilder()
                   .moveTo(-75, -100)
                   .lineTo(75, -100)
                   .lineTo(75, 100)
                   .arcTo(SkRect::MakeLTRB(-20, 80, 20, 120), 0, 90, false)
                   .arcTo(SkRect::MakeLTRB(-20, 80, 20, 120), 90, 90, false)
                   .lineTo(-75, 100)
                   .detach();

        fLightPos = SkPoint3::Make(350, 0, 600);
    }

    bool onChar(SkUnichar uni) override {
            bool handled = false;
            switch (uni) {
                case 'W':
                    fShowAmbient = !fShowAmbient;
                    handled = true;
                    break;
                case 'S':
                    fShowSpot = !fShowSpot;
                    handled = true;
                    break;
                case 'T':
                    fUseAlt = !fUseAlt;
                    handled = true;
                    break;
                case 'B':
                    fUseBlur = !fUseBlur;
                    handled = true;
                    break;
                case 'O':
                    fShowObject = !fShowObject;
                    handled = true;
                    break;
                case 'N':
                    fDoAlphaAnimation = !fDoAlphaAnimation;
                    if (!fDoAlphaAnimation) {
                        fAnimAlpha = 1;
                    }
                    handled = true;
                    break;
                case '>':
                    fZDelta += 0.5f;
                    handled = true;
                    break;
                case '<':
                    fZDelta -= 0.5f;
                    handled = true;
                    break;
                case '?':
                    fIgnoreShadowAlpha = !fIgnoreShadowAlpha;
                    handled = true;
                    break;
                default:
                    break;
            }
            if (handled) {
                return true;
            }
            return false;
    }


    bool animate(double nanos) override {
        fAnimTranslate = TimeUtils::PingPong(1e-9 * nanos, 30, 0, 125, -125);
        fAnimAngle = TimeUtils::PingPong(1e-9 * nanos, 15, 0, 0, 20);
        if (fDoAlphaAnimation) {
            fAnimAlpha = TimeUtils::PingPong(1e-9 * nanos, 5, 0, 1, 0);
        }
        return true;
    }

    void draw(SkCanvas* canvas) override {
        canvas->drawColor(0xFFDDDDDD);

        const SkScalar kLightWidth = 800;
        const SkScalar kAmbientAlpha = 0.039f;
        const SkScalar kSpotAlpha = 0.19f;

        SkPaint paint;
        paint.setAntiAlias(true);

        SkPoint3 lightPos = fLightPos;
        SkPoint3 zPlaneParams = SkPoint3::Make(0, 0, 0);

        paint.setColor(SK_ColorWHITE);
        canvas->translate(200, 90);
        zPlaneParams.fZ = std::max(1.0f, 2 + fZDelta);
        this->drawShadowedPath(canvas, fRRPath, zPlaneParams, paint, fAnimAlpha*kAmbientAlpha,
                               lightPos, kLightWidth, fAnimAlpha*kSpotAlpha);

        paint.setColor(SK_ColorRED);
        canvas->translate(250, 0);
        zPlaneParams.fZ = std::max(1.0f, 8 + fZDelta);
        this->drawShadowedPath(canvas, fRectPath, zPlaneParams, paint, fAnimAlpha*kAmbientAlpha,
                               lightPos, kLightWidth, fAnimAlpha*kSpotAlpha);

        paint.setColor(SK_ColorBLUE);
        canvas->translate(-250, 110);
        zPlaneParams.fZ = std::max(1.0f, 12 + fZDelta);
        this->drawShadowedPath(canvas, fCirclePath, zPlaneParams, paint, fAnimAlpha*kAmbientAlpha,
                               lightPos, kLightWidth, fAnimAlpha*0.5f);

        paint.setColor(SK_ColorGREEN);
        canvas->translate(250, 0);
        zPlaneParams.fZ = std::max(1.0f, 64 + fZDelta);
        this->drawShadowedPath(canvas, fRRPath, zPlaneParams, paint, fAnimAlpha*kAmbientAlpha,
                               lightPos, kLightWidth, fAnimAlpha*kSpotAlpha);

        paint.setColor(SK_ColorYELLOW);
        canvas->translate(-250, 110);
        zPlaneParams.fZ = std::max(1.0f, 8 + fZDelta);
        this->drawShadowedPath(canvas, fFunkyRRPath, zPlaneParams, paint, fAnimAlpha*kAmbientAlpha,
                               lightPos, kLightWidth, fAnimAlpha*kSpotAlpha);

        paint.setColor(SK_ColorCYAN);
        canvas->translate(250, 0);
        zPlaneParams.fZ = std::max(1.0f, 16 + fZDelta);
        this->drawShadowedPath(canvas, fCubicPath, zPlaneParams, paint, fAnimAlpha*kAmbientAlpha,
                               lightPos, kLightWidth, fAnimAlpha*kSpotAlpha);

        paint.setColor(SK_ColorWHITE);
        canvas->translate(250, -180);
        zPlaneParams.fZ = std::max(1.0f, 8 + fZDelta);
        this->drawShadowedPath(canvas, fStarPath, zPlaneParams, paint,
                               kAmbientAlpha, lightPos, kLightWidth, kSpotAlpha);

        paint.setColor(SK_ColorWHITE);
        canvas->translate(150, 0);
        zPlaneParams.fZ = std::max(1.0f, 2 + fZDelta);
        this->drawShadowedPath(canvas, fNotchPath, zPlaneParams, paint,
                               kAmbientAlpha, lightPos, kLightWidth, kSpotAlpha);

        paint.setColor(SK_ColorWHITE);
        canvas->translate(200, 0);
        zPlaneParams.fZ = std::max(1.0f, 16 + fZDelta);
        this->drawShadowedPath(canvas, fTabPath, zPlaneParams, paint,
                               kAmbientAlpha, lightPos, kLightWidth, kSpotAlpha);

        // circular reveal
        SkPath tmpClipPath = SkPath::Circle(fAnimTranslate, 0, 60);
        SkPath tmpPath = Op(fSquareRRectPath, tmpClipPath, kIntersect_SkPathOp).value_or(SkPath());

        paint.setColor(SK_ColorMAGENTA);
        canvas->translate(-725, 240);
        zPlaneParams.fZ = std::max(1.0f, 32 + fZDelta);
        this->drawShadowedPath(canvas, tmpPath, zPlaneParams, paint, .1f,
                               lightPos, kLightWidth, .5f);

        // path ops bug
        SkPath tmpClipPathBug = SkPath::Circle(88.0344925f, 0, 60);
        if (auto res = Op(fSquareRRectPath, tmpClipPathBug, kIntersect_SkPathOp)) {
            tmpPath = *res;
        }

        canvas->translate(250, 0);
        zPlaneParams.fZ = std::max(1.0f, 32 + fZDelta);
        this->drawShadowedPath(canvas, tmpPath, zPlaneParams, paint, .1f,
                               lightPos, kLightWidth, .5f);

        // perspective paths
        SkPoint pivot = SkPoint::Make(fWideRectPath.getBounds().width()/2,
                                      fWideRectPath.getBounds().height()/2);
        SkPoint translate = SkPoint::Make(100, 450);
        paint.setColor(SK_ColorWHITE);
        Sk3DView view;
        view.save();
        view.rotateX(fAnimAngle);
        SkMatrix persp;
        view.getMatrix(&persp);
        persp.preTranslate(-pivot.fX, -pivot.fY);
        persp.postTranslate(pivot.fX + translate.fX, pivot.fY + translate.fY);
        canvas->setMatrix(persp);
        SkScalar radians = SkDegreesToRadians(fAnimAngle);
        zPlaneParams = SkPoint3::Make(0,
                                      SkScalarSin(radians),
                                      std::max(1.0f, 16 + fZDelta) - SkScalarSin(radians)*pivot.fY);
        this->drawShadowedPath(canvas, fWideRectPath, zPlaneParams, paint, .1f,
                               lightPos, kLightWidth, .5f);

        pivot = SkPoint::Make(fWideOvalPath.getBounds().width() / 2,
                              fWideOvalPath.getBounds().height() / 2);
        translate = SkPoint::Make(100, 600);
        view.restore();
        view.save();
        view.rotateY(fAnimAngle);
        view.getMatrix(&persp);
        persp.preTranslate(-pivot.fX, -pivot.fY);
        persp.postTranslate(pivot.fX + translate.fX, pivot.fY + translate.fY);
        canvas->setMatrix(persp);
        zPlaneParams = SkPoint3::Make(-SkScalarSin(radians),
                                      0,
                                      std::max(1.0f, 32 + fZDelta) + SkScalarSin(radians)*pivot.fX);
        this->drawShadowedPath(canvas, fWideOvalPath, zPlaneParams, paint, .1f,
                               lightPos, kLightWidth, .5f);

        pivot = SkPoint::Make(fStarPath.getBounds().width() / 2,
                              fStarPath.getBounds().height() / 2);
        translate = SkPoint::Make(700, 250);
        view.restore();
        view.rotateY(fAnimAngle);
        view.getMatrix(&persp);
        persp.preTranslate(-pivot.fX, -pivot.fY);
        persp.postTranslate(pivot.fX + translate.fX, pivot.fY + translate.fY);
        canvas->setMatrix(persp);
        zPlaneParams = SkPoint3::Make(-SkScalarSin(radians),
                                      0,
                                      std::max(1.0f, 8 + fZDelta) + SkScalarSin(radians)*pivot.fX);
        this->drawShadowedPath(canvas, fStarPath, zPlaneParams, paint, .1f,
                               lightPos, kLightWidth, .5f);
    }

private:
    void drawShadowedPath(SkCanvas* canvas, const SkPath& path,
                          const SkPoint3& zPlaneParams,
                          const SkPaint& paint, SkScalar ambientAlpha,
                          const SkPoint3& lightPos, SkScalar lightWidth, SkScalar spotAlpha) {
        if (fIgnoreShadowAlpha) {
            ambientAlpha = 1;
            spotAlpha = 1;
        }
        if (!fShowAmbient) {
            ambientAlpha = 0;
        }
        if (!fShowSpot) {
            spotAlpha = 0;
        }
        uint32_t flags = 0;
        if (fUseAlt) {
            flags |= SkShadowFlags::kGeometricOnly_ShadowFlag;
        }
        if (fUseBlur) {
            flags |= SkShadowFlags::kConcaveBlurOnly_ShadowFlag;
        }

        SkColor ambientColor = SkColorSetARGB(ambientAlpha * 255, 0, 0, 0);
        SkColor spotColor = SkColorSetARGB(spotAlpha * 255, 0, 0, 0);
        SkShadowUtils::DrawShadow(canvas, path, zPlaneParams, lightPos, lightWidth,
                                  ambientColor, spotColor, flags);

        if (fShowObject) {
            canvas->drawPath(path, paint);
        } else {
            SkPaint strokePaint;

            strokePaint.setColor(paint.getColor());
            strokePaint.setStyle(SkPaint::kStroke_Style);

            canvas->drawPath(path, strokePaint);
        }
    }
};

//////////////////////////////////////////////////////////////////////////////

DEF_SLIDE( return new ShadowsSlide(); )
