//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this sample source code is subject to the terms of the Microsoft
// license agreement under which you licensed this sample source code. If
// you did not accept the terms of the license agreement, you are not
// authorized to use this sample source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the LICENSE.RTF on your install media or the root of your tools installation.
// THE SAMPLE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES.
//

#include <pmimpl.h>

// pointers to battery APIs
typedef DWORD (WINAPI * PFN_GetSystemPowerStatusEx2)(PSYSTEM_POWER_STATUS_EX2, DWORD, BOOL);
typedef LONG (WINAPI * PFN_BatteryDrvrGetLevels)(void);
static PFN_GetSystemPowerStatusEx2 gpfnGetSystemPowerStatusEx2;
static PFN_BatteryDrvrGetLevels gpfnBatteryDrvrGetLevels;

// accesses to this variable should be protected by a critical section
extern "C" extern POWER_BROADCAST_POWER_INFO gSystemPowerStatus;

// This routine initializes variables used to update the platform's power status.
// It is called during PM initialization, so it doesn't take the PM lock before
// accessing gSystemPowerStatus.  It returns TRUE if battery APIs are supported
// on the platform, FALSE otherwise.
BOOL
PmInitPowerStatus(HMODULE hmCoreDll)
{
    BOOL fGotBatteryAPIs;
    DEBUGCHK(hmCoreDll != NULL);

    // initialize battery monitoring variables to "unknown status"
    memset(&gSystemPowerStatus, 0xFF, sizeof(gSystemPowerStatus));

    // get pointers to power information routines
    gpfnGetSystemPowerStatusEx2 = (PFN_GetSystemPowerStatusEx2) GetProcAddress(hmCoreDll,
        _T("GetSystemPowerStatusEx2"));
    gpfnBatteryDrvrGetLevels = (PFN_BatteryDrvrGetLevels) GetProcAddress(hmCoreDll,
        _T("BatteryDrvrGetLevels"));
    DEBUGCHK((gpfnGetSystemPowerStatusEx2 == NULL && gpfnBatteryDrvrGetLevels == NULL) 
        || (gpfnGetSystemPowerStatusEx2 != NULL && gpfnBatteryDrvrGetLevels != NULL));

    // did we find the battery APIs?
    if(gpfnGetSystemPowerStatusEx2 != NULL && gpfnBatteryDrvrGetLevels != NULL) {
        fGotBatteryAPIs = TRUE;
    } else {
        fGotBatteryAPIs = FALSE;
    }

    return fGotBatteryAPIs;
}


// This routine refreshes the PM's power level and source variables.  If the 
// data that would be sent with a PBT_POWERINFOCHANGE notification has been 
// modified, it generates the appropriate notifications.  If the power source
// has changed, the routine returns TRUE.  Otherwise it returns FALSE.
//
// Note that this routine does not set the FORCE flag in GetSystemPowerStatusEx2().
// It assumes that the battery driver has updated its internal cache information
// before sending a message to the PM.  Also, it assumes that battery APIs are ready
// before it receives a notification.
BOOL
PmUpdatePowerStatus(void)
{
    static POWER_BROADCAST_POWER_INFO pi;
    BOOL fACLineStatusChange = FALSE;
    SETFNAME(_T("PmUpdatePowerStatus"));

    PREFAST_DEBUGCHK(gpfnGetSystemPowerStatusEx2 != NULL);
    PREFAST_DEBUGCHK(gpfnBatteryDrvrGetLevels != NULL);

    // do we have battery level information?
    if(pi.dwNumLevels == 0) {
        pi.dwNumLevels = gpfnBatteryDrvrGetLevels();
        DEBUGCHK(pi.dwNumLevels != 0);
    }

    // construct a new power broadcast data structure
    if(pi.dwNumLevels != 0) {
        SYSTEM_POWER_STATUS_EX2 spsCurrent;
        BOOL fPowerInfoChange = FALSE;
        
        DWORD dwLen = gpfnGetSystemPowerStatusEx2(&spsCurrent, sizeof(spsCurrent), FALSE);
        if(dwLen == 0) {
            PMLOGMSG(ZONE_WARN, (_T("%s: GetSystemPowerStatusEx2() failed\r\n"), 
                pszFname));
        } else {
            // fill in our new data structure
            pi.dwBatteryLifeTime = spsCurrent.BatteryLifeTime;
            pi.dwBatteryFullLifeTime = spsCurrent.BatteryFullLifeTime;
            pi.dwBackupBatteryLifeTime = spsCurrent.BackupBatteryLifeTime;
            pi.dwBackupBatteryFullLifeTime = spsCurrent.BackupBatteryFullLifeTime;
            pi.bACLineStatus = spsCurrent.ACLineStatus;
            pi.bBatteryFlag = spsCurrent.BatteryFlag;
            pi.bBatteryLifePercent = spsCurrent.BatteryLifePercent;
            pi.bBackupBatteryFlag = spsCurrent.BackupBatteryFlag;
            pi.bBackupBatteryLifePercent = spsCurrent.BackupBatteryLifePercent;
        }

        // determine whether we need to send out any notifications
        PMLOCK();
        if(memcmp(&gSystemPowerStatus, &pi, sizeof(gSystemPowerStatus)) != 0) {
            fPowerInfoChange = TRUE;
            if(gSystemPowerStatus.bACLineStatus != pi.bACLineStatus) {
                fACLineStatusChange = TRUE;
            }
            gSystemPowerStatus = pi;
        }
        PMUNLOCK();

        // send AC power notifications
        if(fACLineStatusChange) {
            POWER_BROADCAST pb;
            
            // yes, generate a notification
            PMLOGMSG(ZONE_PLATFORM, (_T("%s: AC line status changed to %s\r\n"),
                pszFname, 
                pi.bACLineStatus == AC_LINE_OFFLINE ? _T("offline") : 
                pi.bACLineStatus == AC_LINE_ONLINE ? _T("online") :
                pi.bACLineStatus == AC_LINE_UNKNOWN ? _T("unknown") : _T("<INVALID VALUE>")));
            pb.Message = PBT_POWERSTATUSCHANGE;
            pb.Flags = 0;
            pb.Length = 0;
            pb.SystemPowerState[0] = 0;
            GenerateNotifications(&pb);
        }

        // send power info change notifications
        if(fPowerInfoChange) {
            POWER_BROADCAST_BUFFER pbb;
            PPOWER_BROADCAST_POWER_INFO ppbpi = (PPOWER_BROADCAST_POWER_INFO) pbb.SystemPowerState;
            
            // update the notification buffer
            pbb.Message = PBT_POWERINFOCHANGE;
            pbb.Flags = 0;
            pbb.Length = sizeof(*ppbpi);
            *ppbpi = pi;
            GenerateNotifications((PPOWER_BROADCAST) &pbb);
        }
    }

    return fACLineStatusChange;
}
