/*
 * 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.internal.os;

import java.io.PrintWriter;
import java.io.StringWriter;

import android.os.BatteryStats;
import android.os.Parcel;
import android.support.test.filters.SmallTest;
import android.util.Log;

import junit.framework.Assert;
import junit.framework.TestCase;

import org.mockito.Mockito;

/**
 * Provides test cases for android.os.BatteryStats.
 */
public class BatteryStatsTimeBaseTest extends TestCase {
    private static final String TAG = "BatteryStatsTimeBaseTest";

    static class TestTimeBase extends BatteryStatsImpl.TimeBase {

        public void populate(long uptime, long realtime, boolean running, long pastUptime,
                long uptimeStart, long pastRealtime, long realtimeStart,
                long unpluggedUptime, long unpluggedRealtime) {
            mUptime = uptime;
            mRealtime = realtime;
            mRunning = running;
            mPastUptime = pastUptime;
            mUptimeStart = uptimeStart;
            mPastRealtime = pastRealtime;
            mRealtimeStart = realtimeStart;
            mUnpluggedUptime = unpluggedUptime;
            mUnpluggedRealtime = unpluggedRealtime;
        }

        public void verify(long uptime, long realtime, boolean running, long pastUptime,
                long uptimeStart, long pastRealtime, long realtimeStart,
                long unpluggedUptime, long unpluggedRealtime) {
            Assert.assertEquals(uptime, mUptime);
            Assert.assertEquals(realtime, mRealtime);
            Assert.assertEquals(running, mRunning);
            Assert.assertEquals(pastUptime, mPastUptime);
            Assert.assertEquals(uptimeStart, mUptimeStart);
            Assert.assertEquals(pastRealtime, mPastRealtime);
            Assert.assertEquals(realtimeStart, mRealtimeStart);
            Assert.assertEquals(unpluggedUptime, mUnpluggedUptime);
            Assert.assertEquals(unpluggedRealtime, mUnpluggedRealtime);
        }
    }

    /**
     * Test the observers and the setRunning call.
     */
    @SmallTest
    public void testRunning() throws Exception {
        TestTimeBase tb = new TestTimeBase();

        // Toggle running once, to accumulate past uptime and past realtime
        // so the test values aren't 0.
        tb.setRunning(true, 100, 10000);
        tb.setRunning(false, 200, 11000);
        Assert.assertEquals(100, tb.getUptimeStart());
        Assert.assertEquals(10000, tb.getRealtimeStart());

        // Create some observers
        BatteryStatsImpl.TimeBaseObs observer1 = Mockito.mock(BatteryStatsImpl.TimeBaseObs.class);
        BatteryStatsImpl.TimeBaseObs observer2 = Mockito.mock(BatteryStatsImpl.TimeBaseObs.class);
        BatteryStatsImpl.TimeBaseObs observer3 = Mockito.mock(BatteryStatsImpl.TimeBaseObs.class);

        // Add them
        tb.add(observer1);
        tb.add(observer2);
        tb.add(observer3);
        Assert.assertTrue(tb.hasObserver(observer1));
        Assert.assertTrue(tb.hasObserver(observer2));
        Assert.assertTrue(tb.hasObserver(observer3));

        // Remove one
        tb.remove(observer3);
        Assert.assertTrue(tb.hasObserver(observer1));
        Assert.assertTrue(tb.hasObserver(observer2));
        Assert.assertFalse(tb.hasObserver(observer3));

        // Start running, make sure we get a started call on the two active observers
        // and not the third.
        tb.setRunning(true, 250, 14000);

        Assert.assertTrue(tb.isRunning());

        if (false) {
            Log.d(TAG, "mUptimeStart=" + tb.getUptimeStart()
                    + " mRealtimeStart=" + tb.getRealtimeStart()
                    + " mUptime=" + tb.getUptime(250)
                    + " mRealtime=" + tb.getRealtime(14000)
                    + " isRunning=" + tb.isRunning());
        }

        Assert.assertEquals(250, tb.getUptimeStart());
        Assert.assertEquals(14000, tb.getRealtimeStart());
        Assert.assertEquals(100, tb.getUptime(250));
        Assert.assertEquals(1000, tb.getRealtime(14000));

        Mockito.verify(observer1).onTimeStarted(14000, 100, 1000);
        Mockito.verify(observer1, Mockito.never()).onTimeStopped(-1, -1, -1);
        Mockito.verifyNoMoreInteractions(observer1);
        Mockito.verify(observer2).onTimeStarted(14000, 100, 1000);
        Mockito.verify(observer2, Mockito.never()).onTimeStopped(-1, -1, -1);
        Mockito.verifyNoMoreInteractions(observer2);

        Mockito.reset(observer1);
        Mockito.reset(observer2);
        Mockito.reset(observer3);

        // Advance the "timer" and make sure the getters account for the current time passed in
        Assert.assertEquals(400, tb.getUptime(550));
        Assert.assertEquals(1555, tb.getRealtime(14555));

        // Stop running, make sure we get a stopped call on the two active observers
        // and not the third.
        tb.setRunning(false, 402, 14002);

        Assert.assertFalse(tb.isRunning());

        if (false) {
            Log.d(TAG, "mUptimeStart=" + tb.getUptimeStart()
                    + " mRealtimeStart=" + tb.getRealtimeStart()
                    + " mUptime=" + tb.getUptime(250)
                    + " mRealtime=" + tb.getRealtime(14000)
                    + " isRunning=" + tb.isRunning());
        }

        Assert.assertEquals(252, tb.getUptime(402));
        Assert.assertEquals(1002, tb.getRealtime(14002));

        Mockito.verify(observer1).onTimeStopped(14002, 252, 1002);
        Mockito.verify(observer1, Mockito.never()).onTimeStopped(-1, -1, -1);
        Mockito.verifyNoMoreInteractions(observer1);
        Mockito.verify(observer2).onTimeStopped(14002, 252, 1002);
        Mockito.verify(observer2, Mockito.never()).onTimeStopped(-1, -1, -1);
        Mockito.verifyNoMoreInteractions(observer2);

        // Advance the "timer" and make sure the getters account for the current time passed in
        // is the same as the time when running went to false.
        Assert.assertEquals(252, tb.getUptime(600));
        Assert.assertEquals(1002, tb.getRealtime(17000));
    }

    /**
     * Test that reset while running updates the plugged and unplugged times
     */
    @SmallTest
    public void testResetWhileRunning() throws Exception {
        TestTimeBase tb = new TestTimeBase();
        tb.populate(100, 200, true, 300, 400, 500, 600, 700, 800);

        tb.reset(666, 6666);

        // Not sure if this is a bug: reset while running does not
        // reset mPastUptime, but while it is running it does.
        tb.verify(100, 200, true, 300, 666, 500, 6666, 300, 500);
    }

    /**
     * Test that reset while running updates the plugged and unplugged times
     */
    @SmallTest
    public void testResetWhileNotRunning() throws Exception {
        TestTimeBase tb = new TestTimeBase();
        tb.populate(100, 200, false, 300, 400, 500, 600, 700, 800);

        tb.reset(666, 6666);

        tb.verify(100, 200, false, 0, 400, 0, 600, 700, 800);
    }

    /**
     * Test init
     */
    @SmallTest
    public void testInit() throws Exception {
        TestTimeBase tb = new TestTimeBase();
        tb.populate(100, 200, false, 300, 400, 500, 600, 700, 800);

        tb.init(666, 6666);

        tb.verify(0, 0, false, 0, 666, 0, 6666, 0, 0);
    }

    /**
     * Test writeToParcel and readFromParcel
     */
    @SmallTest
    public void testParcellingWhileRunning() throws Exception {
        TestTimeBase tb1 = new TestTimeBase();

        tb1.populate(100, 200, true, 300, 400, 500, 600, 700, 800);

        Parcel parcel = Parcel.obtain();
        tb1.writeToParcel(parcel, 666, 6666);

        parcel.setDataPosition(0);

        TestTimeBase tb2 = new TestTimeBase();
        tb2.readFromParcel(parcel);

        // Running is not preserved across parceling
        tb2.verify(100, 200, false, 300+666-400, 400, 500+6666-600, 600, 700, 800);
    }

    /**
     * Test writeToParcel and readFromParcel
     */
    @SmallTest
    public void testParcellingWhileNotRunning() throws Exception {
        TestTimeBase tb1 = new TestTimeBase();

        tb1.populate(100, 200, false, 300, 400, 500, 600, 700, 800);

        Parcel parcel = Parcel.obtain();
        tb1.writeToParcel(parcel, 666, 6666);

        parcel.setDataPosition(0);

        TestTimeBase tb2 = new TestTimeBase();
        tb2.readFromParcel(parcel);

        tb2.verify(100, 200, false, 300, 400, 500, 600, 700, 800);
    }

    /**
     * Test writeSummaryToParcel and readSummaryFromParcel
     */
    @SmallTest
    public void testSummary() throws Exception {
        TestTimeBase tb1 = new TestTimeBase();

        tb1.populate(100, 200, true, 300, 400, 500, 600, 700, 800);

        Parcel parcel = Parcel.obtain();
        tb1.writeSummaryToParcel(parcel, 666, 6666);

        parcel.setDataPosition(0);

        TestTimeBase tb2 = new TestTimeBase();

        // readSummaryFromParcel doesn't affect the other fields.
        // Not sure if this is deliberate
        tb2.populate(1, 2, true, 3, 4, 5, 6, 7, 8);

        tb2.readSummaryFromParcel(parcel);

        tb2.verify(666, 6766, true, 3, 4, 5, 6, 7, 8);
    }

    /**
     * Test computeUptime
     */
    @SmallTest
    public void testComputeUptime() throws Exception {
        TestTimeBase tb = new TestTimeBase();

        tb.populate(100, 200, true, 300, 400, 500, 600, 50, 60);

        Assert.assertEquals(100+300+666-400,
                tb.computeUptime(666, BatteryStats.STATS_SINCE_CHARGED));
        Assert.assertEquals(300+666-400,
                tb.computeUptime(666, BatteryStats.STATS_CURRENT));
        Assert.assertEquals(300+666-400-50,
                tb.computeUptime(666, BatteryStats.STATS_SINCE_UNPLUGGED));

        Assert.assertEquals(0, tb.computeUptime(666, 6000));
    }

    /**
     * Test computeUptime
     */
    @SmallTest
    public void testComputeRealtime() throws Exception {
        TestTimeBase tb = new TestTimeBase();

        tb.populate(100, 200, true, 300, 400, 500, 600, 50, 60);

        Assert.assertEquals(200+500+6666-600,
                tb.computeRealtime(6666, BatteryStats.STATS_SINCE_CHARGED));
        Assert.assertEquals(500+6666-600,
                tb.computeRealtime(6666, BatteryStats.STATS_CURRENT));
        Assert.assertEquals(500+6666-600-60,
                tb.computeRealtime(6666, BatteryStats.STATS_SINCE_UNPLUGGED));

        Assert.assertEquals(0, tb.computeUptime(666, 6000));
    }

    /**
     * Test dump
     */
    @SmallTest
    public void testDump() throws Exception {
        TestTimeBase tb = new TestTimeBase();

        tb.populate(100, 200, true, 300, 400, 500, 600, 50, 60);

        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);

        tb.dump(pw, "+++++ ");

        pw.close();

        // note the spaces at the ends of the lines which come from formatTimeMs.
        final String CORRECT = "+++++ mRunning=true\n"
                + "+++++ mUptime=0ms \n"
                + "+++++ mRealtime=0ms \n"
                + "+++++ mPastUptime=0ms mUptimeStart=0ms mUnpluggedUptime=0ms \n"
                + "+++++ mPastRealtime=0ms mRealtimeStart=0ms mUnpluggedRealtime=0ms \n";

        Assert.assertEquals(CORRECT, sw.toString());
    }

}

