/*
 * Copyright (C) 2018 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.systemui.statusbar.notification.stack;

import android.content.res.Resources;
import android.util.MathUtils;
import android.view.View;

import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.phone.KeyguardBypassController;

import java.util.HashSet;

import javax.inject.Inject;

/**
 * A class that manages the roundness for notification views
 */
@SysUISingleton
public class NotificationRoundnessManager {

    private final ExpandableView[] mFirstInSectionViews;
    private final ExpandableView[] mLastInSectionViews;
    private final ExpandableView[] mTmpFirstInSectionViews;
    private final ExpandableView[] mTmpLastInSectionViews;
    private final KeyguardBypassController mBypassController;
    private boolean mExpanded;
    private HashSet<ExpandableView> mAnimatedChildren;
    private Runnable mRoundingChangedCallback;
    private ExpandableNotificationRow mTrackedHeadsUp;
    private float mAppearFraction;

    private ExpandableView mSwipedView = null;
    private ExpandableView mViewBeforeSwipedView = null;
    private ExpandableView mViewAfterSwipedView = null;

    @Inject
    NotificationRoundnessManager(
            KeyguardBypassController keyguardBypassController,
            NotificationSectionsFeatureManager sectionsFeatureManager) {
        int numberOfSections = sectionsFeatureManager.getNumberOfBuckets();
        mFirstInSectionViews = new ExpandableView[numberOfSections];
        mLastInSectionViews = new ExpandableView[numberOfSections];
        mTmpFirstInSectionViews = new ExpandableView[numberOfSections];
        mTmpLastInSectionViews = new ExpandableView[numberOfSections];
        mBypassController = keyguardBypassController;
    }

    public void updateView(ExpandableView view, boolean animate) {
        boolean changed = updateViewWithoutCallback(view, animate);
        if (changed) {
            mRoundingChangedCallback.run();
        }
    }

    boolean updateViewWithoutCallback(ExpandableView view,
            boolean animate) {
        if (view == null
                || view == mViewBeforeSwipedView
                || view == mViewAfterSwipedView) {
            return false;
        }
        float topRoundness = getRoundness(view, true /* top */);
        float bottomRoundness = getRoundness(view, false /* top */);
        boolean topChanged = view.setTopRoundness(topRoundness, animate);
        boolean bottomChanged = view.setBottomRoundness(bottomRoundness, animate);
        boolean firstInSection = isFirstInSection(view, false /* exclude first section */);
        boolean lastInSection = isLastInSection(view, false /* exclude last section */);
        view.setFirstInSection(firstInSection);
        view.setLastInSection(lastInSection);
        return (firstInSection || lastInSection) && (topChanged || bottomChanged);
    }

    private boolean isFirstInSection(ExpandableView view, boolean includeFirstSection) {
        int numNonEmptySections = 0;
        for (int i = 0; i < mFirstInSectionViews.length; i++) {
            if (view == mFirstInSectionViews[i]) {
                return includeFirstSection || numNonEmptySections > 0;
            }
            if (mFirstInSectionViews[i] != null) {
                numNonEmptySections++;
            }
        }
        return false;
    }

    private boolean isLastInSection(ExpandableView view, boolean includeLastSection) {
        int numNonEmptySections = 0;
        for (int i = mLastInSectionViews.length - 1; i >= 0; i--) {
            if (view == mLastInSectionViews[i]) {
                return includeLastSection || numNonEmptySections > 0;
            }
            if (mLastInSectionViews[i] != null) {
                numNonEmptySections++;
            }
        }
        return false;
    }

    void setViewsAffectedBySwipe(
            ExpandableView viewBefore,
            ExpandableView viewSwiped,
            ExpandableView viewAfter,
            boolean cornerAnimationsEnabled) {
        if (!cornerAnimationsEnabled) {
            return;
        }
        final boolean animate = true;

        ExpandableView oldViewBefore = mViewBeforeSwipedView;
        mViewBeforeSwipedView = viewBefore;
        if (oldViewBefore != null) {
            final float bottomRoundness = getRoundness(oldViewBefore, false /* top */);
            oldViewBefore.setBottomRoundness(bottomRoundness,  animate);
        }
        if (viewBefore != null) {
            viewBefore.setBottomRoundness(1f, animate);
        }

        ExpandableView oldSwipedview = mSwipedView;
        mSwipedView = viewSwiped;
        if (oldSwipedview != null) {
            final float bottomRoundness = getRoundness(oldSwipedview, false /* top */);
            final float topRoundness = getRoundness(oldSwipedview, true /* top */);
            oldSwipedview.setTopRoundness(topRoundness, animate);
            oldSwipedview.setBottomRoundness(bottomRoundness, animate);
        }
        if (viewSwiped != null) {
            viewSwiped.setTopRoundness(1f, animate);
            viewSwiped.setBottomRoundness(1f, animate);
        }

        ExpandableView oldViewAfter = mViewAfterSwipedView;
        mViewAfterSwipedView = viewAfter;
        if (oldViewAfter != null) {
            final float topRoundness = getRoundness(oldViewAfter, true /* top */);
            oldViewAfter.setTopRoundness(topRoundness, animate);
        }
        if (viewAfter != null) {
            viewAfter.setTopRoundness(1f, animate);
        }
    }

    private float getRoundness(ExpandableView view, boolean top) {
        if (view == null) {
            return 0f;
        }
        if (view == mViewBeforeSwipedView
                || view == mSwipedView
                || view == mViewAfterSwipedView) {
            return 1f;
        }
        if ((view.isPinned()
                || (view.isHeadsUpAnimatingAway()) && !mExpanded)) {
            return 1.0f;
        }
        if (isFirstInSection(view, true /* include first section */) && top) {
            return 1.0f;
        }
        if (isLastInSection(view, true /* include last section */) && !top) {
            return 1.0f;
        }
        if (view == mTrackedHeadsUp) {
            // If we're pushing up on a headsup the appear fraction is < 0 and it needs to still be
            // rounded.
            return MathUtils.saturate(1.0f - mAppearFraction);
        }
        if (view.showingPulsing() && !mBypassController.getBypassEnabled()) {
            return 1.0f;
        }
        final Resources resources = view.getResources();
        return resources.getDimension(R.dimen.notification_corner_radius_small)
                / resources.getDimension(R.dimen.notification_corner_radius);
    }

    public void setExpanded(float expandedHeight, float appearFraction) {
        mExpanded = expandedHeight != 0.0f;
        mAppearFraction = appearFraction;
        if (mTrackedHeadsUp != null) {
            updateView(mTrackedHeadsUp, true);
        }
    }

    public void updateRoundedChildren(NotificationSection[] sections) {
        boolean anyChanged = false;
        for (int i = 0; i < sections.length; i++) {
            mTmpFirstInSectionViews[i] = mFirstInSectionViews[i];
            mTmpLastInSectionViews[i] = mLastInSectionViews[i];
            mFirstInSectionViews[i] = sections[i].getFirstVisibleChild();
            mLastInSectionViews[i] = sections[i].getLastVisibleChild();
        }
        anyChanged |= handleRemovedOldViews(sections, mTmpFirstInSectionViews, true);
        anyChanged |= handleRemovedOldViews(sections, mTmpLastInSectionViews, false);
        anyChanged |= handleAddedNewViews(sections, mTmpFirstInSectionViews, true);
        anyChanged |= handleAddedNewViews(sections, mTmpLastInSectionViews, false);
        if (anyChanged) {
            mRoundingChangedCallback.run();
        }
    }

    private boolean handleRemovedOldViews(NotificationSection[] sections,
            ExpandableView[] oldViews, boolean first) {
        boolean anyChanged = false;
        for (ExpandableView oldView : oldViews) {
            if (oldView != null) {
                boolean isStillPresent = false;
                boolean adjacentSectionChanged = false;
                for (NotificationSection section : sections) {
                    ExpandableView newView =
                            (first ? section.getFirstVisibleChild()
                                    : section.getLastVisibleChild());
                    if (newView == oldView) {
                        isStillPresent = true;
                        if (oldView.isFirstInSection() != isFirstInSection(oldView,
                                false /* exclude first section */)
                                || oldView.isLastInSection() != isLastInSection(oldView,
                                false /* exclude last section */)) {
                            adjacentSectionChanged = true;
                        }
                        break;
                    }
                }
                if (!isStillPresent || adjacentSectionChanged) {
                    anyChanged = true;
                    if (!oldView.isRemoved()) {
                        updateViewWithoutCallback(oldView, oldView.isShown());
                    }
                }
            }
        }
        return anyChanged;
    }

    private boolean handleAddedNewViews(NotificationSection[] sections,
            ExpandableView[] oldViews, boolean first) {
        boolean anyChanged = false;
        for (NotificationSection section : sections) {
            ExpandableView newView =
                    (first ? section.getFirstVisibleChild() : section.getLastVisibleChild());
            if (newView != null) {
                boolean wasAlreadyPresent = false;
                for (ExpandableView oldView : oldViews) {
                    if (oldView == newView) {
                        wasAlreadyPresent = true;
                        break;
                    }
                }
                if (!wasAlreadyPresent) {
                    anyChanged = true;
                    updateViewWithoutCallback(newView,
                            newView.isShown() && !mAnimatedChildren.contains(newView));
                }
            }
        }
        return anyChanged;
    }

    public void setAnimatedChildren(HashSet<ExpandableView> animatedChildren) {
        mAnimatedChildren = animatedChildren;
    }

    public void setOnRoundingChangedCallback(Runnable roundingChangedCallback) {
        mRoundingChangedCallback = roundingChangedCallback;
    }

    public void setTrackingHeadsUp(ExpandableNotificationRow row) {
        ExpandableNotificationRow previous = mTrackedHeadsUp;
        mTrackedHeadsUp = row;
        if (previous != null) {
            updateView(previous, true /* animate */);
        }
    }
}
