/*
 * 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.server.power.batterysaver;

import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import android.metrics.LogMaker;

import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.internal.logging.MetricsLogger;
import com.android.server.power.batterysaver.BatterySavingStats.BatterySaverState;
import com.android.server.power.batterysaver.BatterySavingStats.DozeState;
import com.android.server.power.batterysaver.BatterySavingStats.InteractiveState;

import org.junit.Test;
import org.junit.runner.RunWith;

import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;

/**
 atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySavingStatsTest.java
 */
@SmallTest
@RunWith(AndroidJUnit4.class)
public class BatterySavingStatsTest {
    private class BatterySavingStatsTestable extends BatterySavingStats {
        private long mTime = 1_000_000; // Some random starting time.

        private int mBatteryLevel = 1_000_000_000;

        private BatterySavingStatsTestable() {
            super(new Object());
        }

        @Override
        long injectCurrentTime() {
            return mTime;
        }

        @Override
        int injectBatteryLevel() {
            return mBatteryLevel;
        }

        @Override
        int injectBatteryPercent() {
            return mBatteryLevel / 10;
        }

        void assertDumpable() {
            final ByteArrayOutputStream out = new ByteArrayOutputStream();
            dump(new PrintWriter(out), ""); // Just make sure it won't crash.
        }

        void advanceClock(int minutes) {
            mTime += 60_000 * minutes;
        }

        void drainBattery(int value) {
            mBatteryLevel -= value;
            if (mBatteryLevel < 0) {
                mBatteryLevel = 0;
            }
        }

        String toDebugString() {
            final StringBuilder sb = new StringBuilder();
            String sep = "";
            for (int i = 0; i < mStats.size(); i++) {
                sb.append(sep);
                sb.append(stateToString(mStats.keyAt(i)));
                sb.append(":");
                sb.append(mStats.valueAt(i).toStringForTest());
                sep = "\n";
            }
            return sb.toString();
        }
    }

    public MetricsLogger mMetricsLogger = mock(MetricsLogger.class);

    @Test
    public void testAll() {
        checkAll();
    }

    private void checkAll() {
        final BatterySavingStatsTestable target = new BatterySavingStatsTestable();

        target.assertDumpable();

        target.advanceClock(1);
        target.drainBattery(200);

        target.transitionState(
                BatterySaverState.OFF,
                InteractiveState.INTERACTIVE,
                DozeState.NOT_DOZING);

        target.advanceClock(4);
        target.drainBattery(100);

        target.transitionState(
                BatterySaverState.OFF,
                InteractiveState.NON_INTERACTIVE,
                DozeState.NOT_DOZING);

        target.advanceClock(2);
        target.drainBattery(500);

        target.transitionState(
                BatterySaverState.OFF,
                InteractiveState.INTERACTIVE,
                DozeState.NOT_DOZING);

        target.advanceClock(4);
        target.drainBattery(100);

        target.transitionState(
                BatterySaverState.OFF,
                InteractiveState.NON_INTERACTIVE,
                DozeState.NOT_DOZING);

        target.advanceClock(2);
        target.drainBattery(500);

        target.transitionState(
                BatterySaverState.OFF,
                InteractiveState.INTERACTIVE,
                DozeState.NOT_DOZING);

        target.advanceClock(3);
        target.drainBattery(100);

        target.transitionState(
                BatterySaverState.OFF,
                InteractiveState.NON_INTERACTIVE,
                DozeState.LIGHT);

        target.advanceClock(5);
        target.drainBattery(100);

        target.transitionState(
                BatterySaverState.OFF,
                InteractiveState.NON_INTERACTIVE,
                DozeState.DEEP);

        target.advanceClock(1);
        target.drainBattery(200);

        target.transitionState(
                BatterySaverState.ON,
                InteractiveState.INTERACTIVE,
                DozeState.NOT_DOZING);

        target.advanceClock(1);
        target.drainBattery(300);

        target.transitionState(
                BatterySaverState.OFF,
                InteractiveState.INTERACTIVE,
                DozeState.NOT_DOZING);

        target.advanceClock(3);
        target.drainBattery(500);

        target.transitionState(
                BatterySaverState.ON,
                InteractiveState.INTERACTIVE,
                DozeState.NOT_DOZING);

        target.advanceClock(3);
        target.drainBattery(500);

        target.startCharging();

        target.advanceClock(5);
        target.drainBattery(1000);

        target.transitionState(
                BatterySaverState.ON,
                InteractiveState.INTERACTIVE,
                DozeState.NOT_DOZING);

        target.advanceClock(5);
        target.drainBattery(100);

        target.startCharging();

        target.assertDumpable();

        assertEquals(
                "BS=0,I=0,D=0:{4m,1000,15000.00uA/H,1500.00%}\n" +
                "BS=1,I=0,D=0:{0m,0,0.00uA/H,0.00%}\n" +
                "BS=0,I=1,D=0:{14m,800,3428.57uA/H,342.86%}\n" +
                "BS=1,I=1,D=0:{9m,900,6000.00uA/H,600.00%}\n" +
                "BS=0,I=0,D=1:{5m,100,1200.00uA/H,120.00%}\n" +
                "BS=1,I=0,D=1:{0m,0,0.00uA/H,0.00%}\n" +
                "BS=0,I=1,D=1:{0m,0,0.00uA/H,0.00%}\n" +
                "BS=1,I=1,D=1:{0m,0,0.00uA/H,0.00%}\n" +
                "BS=0,I=0,D=2:{1m,200,12000.00uA/H,1200.00%}\n" +
                "BS=1,I=0,D=2:{0m,0,0.00uA/H,0.00%}\n" +
                "BS=0,I=1,D=2:{0m,0,0.00uA/H,0.00%}\n" +
                "BS=1,I=1,D=2:{0m,0,0.00uA/H,0.00%}",
                target.toDebugString());
    }

    private void assertLog() {
        verify(mMetricsLogger, times(0)).write(any(LogMaker.class));
    }

    @Test
    public void testMetricsLogger() {
        final BatterySavingStatsTestable target = new BatterySavingStatsTestable();

        target.advanceClock(1);
        target.drainBattery(1000);

        target.transitionState(
                BatterySaverState.OFF,
                InteractiveState.INTERACTIVE,
                DozeState.NOT_DOZING);

        verify(mMetricsLogger, times(0)).count(anyString(), anyInt());

        target.advanceClock(1);
        target.drainBattery(2000);

        reset(mMetricsLogger);
        target.transitionState(
                BatterySaverState.OFF,
                InteractiveState.NON_INTERACTIVE,
                DozeState.NOT_DOZING);

        assertLog();

        target.advanceClock(1);
        target.drainBattery(2000);

        reset(mMetricsLogger);
        target.transitionState(
                BatterySaverState.OFF,
                InteractiveState.NON_INTERACTIVE,
                DozeState.DEEP);

        target.advanceClock(1);
        target.drainBattery(2000);

        verify(mMetricsLogger, times(0)).count(anyString(), anyInt());

        target.transitionState(
                BatterySaverState.OFF,
                InteractiveState.NON_INTERACTIVE,
                DozeState.LIGHT);

        target.advanceClock(1);
        target.drainBattery(2000);

        verify(mMetricsLogger, times(0)).count(anyString(), anyInt());

        target.transitionState(
                BatterySaverState.ON,
                InteractiveState.INTERACTIVE,
                DozeState.NOT_DOZING);

        assertLog();

        target.advanceClock(10);
        target.drainBattery(10000);

        reset(mMetricsLogger);
        target.startCharging();

        assertLog();

        target.advanceClock(1);
        target.drainBattery(2000);

        reset(mMetricsLogger);
        target.transitionState(
                BatterySaverState.ON,
                InteractiveState.NON_INTERACTIVE,
                DozeState.NOT_DOZING);

        verify(mMetricsLogger, times(0)).count(anyString(), anyInt());

        target.advanceClock(1);
        target.drainBattery(2000);

        target.startCharging();

        assertLog();
    }
}
