/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
 *
 * 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.launcher3.taskbar;

import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.Keyframe;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.TimeInterpolator;
import android.content.res.Resources;
import android.text.TextUtils;
import android.view.View;

import com.android.launcher3.R;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.uioverrides.PredictedAppIcon;

import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

/** Handles the Taskbar Education flow. */
public class TaskbarEduController {

    private static final long WAVE_ANIM_DELAY = 250;
    private static final long WAVE_ANIM_STAGGER = 50;
    private static final long WAVE_ANIM_EACH_ICON_DURATION = 633;
    private static final long WAVE_ANIM_SLOT_MACHINE_DURATION = 1085;
    // The fraction of each icon's animation at which we reach the top point of the wave.
    private static final float WAVE_ANIM_FRACTION_TOP = 0.4f;
    // The fraction of each icon's animation at which we reach the bottom, before overshooting.
    private static final float WAVE_ANIM_FRACTION_BOTTOM = 0.9f;
    private static final TimeInterpolator WAVE_ANIM_TO_TOP_INTERPOLATOR = FAST_OUT_SLOW_IN;
    private static final TimeInterpolator WAVE_ANIM_TO_BOTTOM_INTERPOLATOR = ACCEL_2;
    private static final TimeInterpolator WAVE_ANIM_OVERSHOOT_INTERPOLATOR = DEACCEL;
    private static final TimeInterpolator WAVE_ANIM_OVERSHOOT_RETURN_INTERPOLATOR = ACCEL_DEACCEL;
    private static final float WAVE_ANIM_ICON_SCALE = 1.2f;
    // How many icons to cycle through in the slot machine (+ the original icon at each end).
    private static final int WAVE_ANIM_SLOT_MACHINE_NUM_ICONS = 3;

    private final TaskbarActivityContext mActivity;
    private final float mWaveAnimTranslationY;
    private final float mWaveAnimTranslationYReturnOvershoot;

    // Initialized in init.
    TaskbarControllers mControllers;

    private TaskbarEduView mTaskbarEduView;
    private Animator mAnim;

    public TaskbarEduController(TaskbarActivityContext activity) {
        mActivity = activity;

        final Resources resources = activity.getResources();
        mWaveAnimTranslationY = resources.getDimension(R.dimen.taskbar_edu_wave_anim_trans_y);
        mWaveAnimTranslationYReturnOvershoot = resources.getDimension(
                R.dimen.taskbar_edu_wave_anim_trans_y_return_overshoot);
    }

    public void init(TaskbarControllers controllers) {
        mControllers = controllers;
    }

    void showEdu() {
        mActivity.setTaskbarWindowFullscreen(true);
        mActivity.getDragLayer().post(() -> {
            mTaskbarEduView = (TaskbarEduView) mActivity.getLayoutInflater().inflate(
                    R.layout.taskbar_edu, mActivity.getDragLayer(), false);
            mTaskbarEduView.init(new TaskbarEduCallbacks());
            mTaskbarEduView.addOnCloseListener(() -> mTaskbarEduView = null);
            mTaskbarEduView.show();
            startAnim(createWaveAnim());
        });
    }

    void hideEdu() {
        if (mTaskbarEduView != null) {
            mTaskbarEduView.close(true /* animate */);
        }
    }

    /**
     * Starts the given animation, ending the previous animation first if it's still playing.
     */
    private void startAnim(Animator anim) {
        if (mAnim != null) {
            mAnim.end();
        }
        mAnim = anim;
        mAnim.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                mAnim = null;
            }
        });
        mAnim.start();
    }

    /**
     * Creates a staggered "wave" animation where each icon translates and scales up in succession.
     */
    private Animator createWaveAnim() {
        AnimatorSet waveAnim = new AnimatorSet();
        View[] icons = mControllers.taskbarViewController.getIconViews();
        for (int i = 0; i < icons.length; i++) {
            View icon = icons[i];
            AnimatorSet iconAnim = new AnimatorSet();

            Keyframe[] scaleKeyframes = new Keyframe[] {
                    Keyframe.ofFloat(0, 1f),
                    Keyframe.ofFloat(WAVE_ANIM_FRACTION_TOP, WAVE_ANIM_ICON_SCALE),
                    Keyframe.ofFloat(WAVE_ANIM_FRACTION_BOTTOM, 1f),
                    Keyframe.ofFloat(1f, 1f)
            };
            scaleKeyframes[1].setInterpolator(WAVE_ANIM_TO_TOP_INTERPOLATOR);
            scaleKeyframes[2].setInterpolator(WAVE_ANIM_TO_BOTTOM_INTERPOLATOR);

            Keyframe[] translationYKeyframes = new Keyframe[] {
                    Keyframe.ofFloat(0, 0f),
                    Keyframe.ofFloat(WAVE_ANIM_FRACTION_TOP, -mWaveAnimTranslationY),
                    Keyframe.ofFloat(WAVE_ANIM_FRACTION_BOTTOM, 0f),
                    // Half of the remaining fraction overshoots, then the other half returns to 0.
                    Keyframe.ofFloat(
                            WAVE_ANIM_FRACTION_BOTTOM + (1 - WAVE_ANIM_FRACTION_BOTTOM) / 2f,
                            mWaveAnimTranslationYReturnOvershoot),
                    Keyframe.ofFloat(1f, 0f)
            };
            translationYKeyframes[1].setInterpolator(WAVE_ANIM_TO_TOP_INTERPOLATOR);
            translationYKeyframes[2].setInterpolator(WAVE_ANIM_TO_BOTTOM_INTERPOLATOR);
            translationYKeyframes[3].setInterpolator(WAVE_ANIM_OVERSHOOT_INTERPOLATOR);
            translationYKeyframes[4].setInterpolator(WAVE_ANIM_OVERSHOOT_RETURN_INTERPOLATOR);

            iconAnim.play(ObjectAnimator.ofPropertyValuesHolder(icon,
                    PropertyValuesHolder.ofKeyframe(SCALE_PROPERTY, scaleKeyframes))
                    .setDuration(WAVE_ANIM_EACH_ICON_DURATION));
            iconAnim.play(ObjectAnimator.ofPropertyValuesHolder(icon,
                    PropertyValuesHolder.ofKeyframe(View.TRANSLATION_Y, translationYKeyframes))
                    .setDuration(WAVE_ANIM_EACH_ICON_DURATION));

            if (icon instanceof PredictedAppIcon) {
                // Play slot machine animation through random icons from AllAppsList.
                PredictedAppIcon predictedAppIcon = (PredictedAppIcon) icon;
                ItemInfo itemInfo = (ItemInfo) icon.getTag();
                List<BitmapInfo> iconsToAnimate = mControllers.uiController.getAppIconsForEdu()
                        .filter(appInfo -> !TextUtils.equals(appInfo.title, itemInfo.title))
                        .map(appInfo -> appInfo.bitmap)
                        .filter(bitmap -> !bitmap.isNullOrLowRes())
                        .collect(Collectors.toList());
                // Pick n icons at random.
                Collections.shuffle(iconsToAnimate);
                if (iconsToAnimate.size() > WAVE_ANIM_SLOT_MACHINE_NUM_ICONS) {
                    iconsToAnimate = iconsToAnimate.subList(0, WAVE_ANIM_SLOT_MACHINE_NUM_ICONS);
                }
                Animator slotMachineAnim = predictedAppIcon.createSlotMachineAnim(iconsToAnimate);
                if (slotMachineAnim != null) {
                    iconAnim.play(slotMachineAnim.setDuration(WAVE_ANIM_SLOT_MACHINE_DURATION));
                }
            }

            iconAnim.setStartDelay(WAVE_ANIM_STAGGER * i);
            waveAnim.play(iconAnim);
        }
        waveAnim.setStartDelay(WAVE_ANIM_DELAY);
        return waveAnim;
    }

    /**
     * Callbacks for {@link TaskbarEduView} to interact with its controller.
     */
    class TaskbarEduCallbacks {
        void onPageChanged(int currentPage, int pageCount) {
            if (currentPage == 0) {
                mTaskbarEduView.updateStartButton(R.string.taskbar_edu_close,
                        v -> mTaskbarEduView.close(true /* animate */));
            } else {
                mTaskbarEduView.updateStartButton(R.string.taskbar_edu_previous,
                        v -> mTaskbarEduView.snapToPage(currentPage - 1));
            }
            if (currentPage == pageCount - 1) {
                mTaskbarEduView.updateEndButton(R.string.taskbar_edu_done,
                        v -> mTaskbarEduView.close(true /* animate */));
            } else {
                mTaskbarEduView.updateEndButton(R.string.taskbar_edu_next,
                        v -> mTaskbarEduView.snapToPage(currentPage + 1));
            }
        }
    }
}
