/*
 * Copyright (C) 2016 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.phone;

import android.app.WallpaperColors;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Rect;
import android.view.View;

import com.android.internal.colorextraction.ColorExtractor.GradientColors;
import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.DarkIconDispatcher;

import java.io.FileDescriptor;
import java.io.PrintWriter;

import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT;

/**
 * Controls how light status bar flag applies to the icons.
 */
public class LightBarController implements BatteryController.BatteryStateChangeCallback, Dumpable {

    private static final float NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD = 0.1f;

    private final DarkIconDispatcher mStatusBarIconController;
    private final BatteryController mBatteryController;
    private FingerprintUnlockController mFingerprintUnlockController;

    private LightBarTransitionsController mNavigationBarController;
    private int mSystemUiVisibility;
    private int mFullscreenStackVisibility;
    private int mDockedStackVisibility;
    private boolean mFullscreenLight;
    private boolean mDockedLight;
    private int mLastStatusBarMode;
    private int mLastNavigationBarMode;
    private final Color mDarkModeColor;

    /**
     * Whether the navigation bar should be light factoring in already how much alpha the scrim has
     */
    private boolean mNavigationLight;

    /**
     * Whether the flags indicate that a light status bar is requested. This doesn't factor in the
     * scrim alpha yet.
     */
    private boolean mHasLightNavigationBar;
    private boolean mScrimAlphaBelowThreshold;
    private boolean mInvertLightNavBarWithScrim;
    private float mScrimAlpha;

    private final Rect mLastFullscreenBounds = new Rect();
    private final Rect mLastDockedBounds = new Rect();

    public LightBarController(Context ctx) {
        mDarkModeColor = Color.valueOf(ctx.getColor(R.color.dark_mode_icon_color_single_tone));
        mStatusBarIconController = Dependency.get(DarkIconDispatcher.class);
        mBatteryController = Dependency.get(BatteryController.class);
        mBatteryController.addCallback(this);
    }

    public void setNavigationBar(LightBarTransitionsController navigationBar) {
        mNavigationBarController = navigationBar;
        updateNavigation();
    }

    public void setFingerprintUnlockController(
            FingerprintUnlockController fingerprintUnlockController) {
        mFingerprintUnlockController = fingerprintUnlockController;
    }

    public void onSystemUiVisibilityChanged(int fullscreenStackVis, int dockedStackVis,
            int mask, Rect fullscreenStackBounds, Rect dockedStackBounds, boolean sbModeChanged,
            int statusBarMode) {
        int oldFullscreen = mFullscreenStackVisibility;
        int newFullscreen = (oldFullscreen & ~mask) | (fullscreenStackVis & mask);
        int diffFullscreen = newFullscreen ^ oldFullscreen;
        int oldDocked = mDockedStackVisibility;
        int newDocked = (oldDocked & ~mask) | (dockedStackVis & mask);
        int diffDocked = newDocked ^ oldDocked;
        if ((diffFullscreen & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0
                || (diffDocked & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0
                || sbModeChanged
                || !mLastFullscreenBounds.equals(fullscreenStackBounds)
                || !mLastDockedBounds.equals(dockedStackBounds)) {

            mFullscreenLight = isLight(newFullscreen, statusBarMode,
                    View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
            mDockedLight = isLight(newDocked, statusBarMode, View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
            updateStatus(fullscreenStackBounds, dockedStackBounds);
        }

        mFullscreenStackVisibility = newFullscreen;
        mDockedStackVisibility = newDocked;
        mLastStatusBarMode = statusBarMode;
        mLastFullscreenBounds.set(fullscreenStackBounds);
        mLastDockedBounds.set(dockedStackBounds);
    }

    public void onNavigationVisibilityChanged(int vis, int mask, boolean nbModeChanged,
            int navigationBarMode) {
        int oldVis = mSystemUiVisibility;
        int newVis = (oldVis & ~mask) | (vis & mask);
        int diffVis = newVis ^ oldVis;
        if ((diffVis & View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) != 0
                || nbModeChanged) {
            boolean last = mNavigationLight;
            mHasLightNavigationBar = isLight(vis, navigationBarMode,
                    View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
            mNavigationLight = mHasLightNavigationBar
                    && (mScrimAlphaBelowThreshold || !mInvertLightNavBarWithScrim);
            if (mNavigationLight != last) {
                updateNavigation();
            }
        }
        mSystemUiVisibility = newVis;
        mLastNavigationBarMode = navigationBarMode;
    }

    private void reevaluate() {
        onSystemUiVisibilityChanged(mFullscreenStackVisibility,
                mDockedStackVisibility, 0 /* mask */, mLastFullscreenBounds, mLastDockedBounds,
                true /* sbModeChange*/, mLastStatusBarMode);
        onNavigationVisibilityChanged(mSystemUiVisibility, 0 /* mask */, true /* nbModeChanged */,
                mLastNavigationBarMode);
    }

    public void setScrimAlpha(float alpha) {
        mScrimAlpha = alpha;
        boolean belowThresholdBefore = mScrimAlphaBelowThreshold;
        mScrimAlphaBelowThreshold = mScrimAlpha < NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD;
        if (mHasLightNavigationBar && belowThresholdBefore != mScrimAlphaBelowThreshold) {
            reevaluate();
        }
    }

    public void setScrimColor(GradientColors colors) {
        boolean invertLightNavBarWithScrimBefore = mInvertLightNavBarWithScrim;
        mInvertLightNavBarWithScrim = !colors.supportsDarkText();
        if (mHasLightNavigationBar
                && invertLightNavBarWithScrimBefore != mInvertLightNavBarWithScrim) {
            reevaluate();
        }
    }

    private boolean isLight(int vis, int barMode, int flag) {
        boolean isTransparentBar = (barMode == MODE_TRANSPARENT
                || barMode == MODE_LIGHTS_OUT_TRANSPARENT);
        boolean allowLight = isTransparentBar && !mBatteryController.isPowerSave();
        boolean light = (vis & flag) != 0;
        return allowLight && light;
    }

    private boolean animateChange() {
        if (mFingerprintUnlockController == null) {
            return false;
        }
        int unlockMode = mFingerprintUnlockController.getMode();
        return unlockMode != FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
                && unlockMode != FingerprintUnlockController.MODE_WAKE_AND_UNLOCK;
    }

    private void updateStatus(Rect fullscreenStackBounds, Rect dockedStackBounds) {
        boolean hasDockedStack = !dockedStackBounds.isEmpty();

        // If both are light or fullscreen is light and there is no docked stack, all icons get
        // dark.
        if ((mFullscreenLight && mDockedLight) || (mFullscreenLight && !hasDockedStack)) {
            mStatusBarIconController.setIconsDarkArea(null);
            mStatusBarIconController.getTransitionsController().setIconsDark(true, animateChange());

        }

        // If no one is light or the fullscreen is not light and there is no docked stack,
        // all icons become white.
        else if ((!mFullscreenLight && !mDockedLight) || (!mFullscreenLight && !hasDockedStack)) {
            mStatusBarIconController.getTransitionsController().setIconsDark(
                    false, animateChange());
        }

        // Not the same for every stack, magic!
        else {
            Rect bounds = mFullscreenLight ? fullscreenStackBounds : dockedStackBounds;
            if (bounds.isEmpty()) {
                mStatusBarIconController.setIconsDarkArea(null);
            } else {
                mStatusBarIconController.setIconsDarkArea(bounds);
            }
            mStatusBarIconController.getTransitionsController().setIconsDark(true, animateChange());
        }
    }

    private void updateNavigation() {
        if (mNavigationBarController != null) {
            mNavigationBarController.setIconsDark(
                    mNavigationLight, animateChange());
        }
    }

    @Override
    public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {

    }

    @Override
    public void onPowerSaveChanged(boolean isPowerSave) {
        reevaluate();
    }

    @Override
    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        pw.println("LightBarController: ");
        pw.print(" mSystemUiVisibility=0x"); pw.print(
                Integer.toHexString(mSystemUiVisibility));
        pw.print(" mFullscreenStackVisibility=0x"); pw.print(
                Integer.toHexString(mFullscreenStackVisibility));
        pw.print(" mDockedStackVisibility=0x"); pw.println(
                Integer.toHexString(mDockedStackVisibility));

        pw.print(" mFullscreenLight="); pw.print(mFullscreenLight);
        pw.print(" mDockedLight="); pw.println(mDockedLight);

        pw.print(" mLastFullscreenBounds="); pw.print(mLastFullscreenBounds);
        pw.print(" mLastDockedBounds="); pw.println(mLastDockedBounds);

        pw.print(" mNavigationLight="); pw.print(mNavigationLight);
        pw.print(" mHasLightNavigationBar="); pw.println(mHasLightNavigationBar);

        pw.print(" mLastStatusBarMode="); pw.print(mLastStatusBarMode);
        pw.print(" mLastNavigationBarMode="); pw.println(mLastNavigationBarMode);

        pw.print(" mScrimAlpha="); pw.print(mScrimAlpha);
        pw.print(" mScrimAlphaBelowThreshold="); pw.println(mScrimAlphaBelowThreshold);
        pw.println();
        pw.println(" StatusBarTransitionsController:");
        mStatusBarIconController.getTransitionsController().dump(fd, pw, args);
        pw.println();
        pw.println(" NavigationBarTransitionsController:");
        mNavigationBarController.dump(fd, pw, args);
        pw.println();
    }
}
