/*!
  \file
  \brief バッテリー状態の取得

  \author Satofumi KAMIMURA

  $Id: BatteryState.cpp 1811 2010-04-30 16:12:05Z satofumi $
*/

#include "BatteryState.h"
#include "DetectOS.h"
#if defined(WINDOWS_OS)
#include <windows.h>
#else
#include "split.h"
#include <algorithm>
#include <string>
#include <fstream>
#include <cstdlib>
#endif

using namespace qrk;
using namespace std;


#if defined(WINDOWS_OS)
struct BatteryState::pImpl
{
    bool isAvailable(void)
    {
        SYSTEM_POWER_STATUS status;
        if (! GetSystemPowerStatus(&status)) {
            return false;
        }
        return (status.BatteryLifePercent == 255) ? false : true;
    }


    bool isCharging(void)
    {
        SYSTEM_POWER_STATUS status;
        if (! GetSystemPowerStatus(&status)) {
            return false;
        }
        return (static_cast<int>(status.ACLineStatus) == 1) ? true : false;
    }


    size_t remainingPercent(void)
    {
        SYSTEM_POWER_STATUS status;
        if (! GetSystemPowerStatus(&status)) {
            return 0;
        }
        return status.BatteryLifePercent;
    }


    size_t remainingSecond(void)
    {
        SYSTEM_POWER_STATUS status;
        if (! GetSystemPowerStatus(&status)) {
            return 0;
        }
        return max(0, static_cast<int>(status.BatteryLifeTime));
    }
};
#else

struct BatteryState::pImpl
{
    bool is_available_;
    size_t full_capacity_;
    string state_file_;
    string info_file_;


    pImpl(void) : is_available_(false), full_capacity_(1)
    {
        // BAT0, BAT1 を順番に試す
        for (size_t id = 0; id < 2; ++id) {
            char buffer[] = "BAT0";
            snprintf(buffer, sizeof(buffer), "BAT%d", id);

            state_file_ = "/proc/acpi/battery/" + string(buffer) + "/state";
            info_file_ = "/proc/acpi/battery/" + string(buffer) + "/info";

            if (loadFullCapacity()) {
                break;
            }
        }
    }


    bool matchData(string& value, const char* file, const char* pattern) const
    {
        ifstream fin(file);
        if (! fin.is_open()) {
            return false;
        }

        // トークンの区切りを ':' とし、得られた結果からは空白を除去して返す
        string line;
        while (getline(fin, line)) {
            vector<string> tokens;
            if (split(tokens, line, ":") >= 2) {
                if (! tokens[0].compare(pattern)) {
                    string& token = tokens[1];
                    size_t first_index = 0;
                    while (token[first_index] == ' ') {
                        ++first_index;
                    }
                    value = &token[first_index];
                    return true;
                }
            }
        }
        return false;
    }


    bool matchData(size_t& value, const char* file, const char* pattern) const
    {
        string string_value;
        if (! matchData(string_value, file, pattern)) {
            return false;
        }

        value = atoi(string_value.c_str());
        return true;
    }


    bool loadFullCapacity(void)
    {
        // /proc/acpi/battery/BATx/info ファイルの "last full capacity"
        // を読み出す
        if ((matchData(full_capacity_,
                       info_file_.c_str(), "last full capacity")) &&
            (full_capacity_ > 0)) {
            is_available_ = true;
            return true;
        }

        return false;
    }


    bool isAvailable(void) const
    {
        return is_available_;
    }


    bool isCharging(void) const
    {
        if (! is_available_) {
            return false;
        }

        // 電源が接続されていないときに false を返す
        // /proc/acpi/battery/BATx/state ファイルの "charging state" を読み出す
        string state;
        if (matchData(state, state_file_.c_str(), "charging state") &&
            ((! state.compare("charged")) || (! state.compare("charging")))) {
            return true;
        }

        return false;
    }


    size_t remainingPercent(void) const
    {
        if (! is_available_) {
            return 0;
        }

        // "/proc/acpi/battery/BATx/state" ファイルの
        // "remaining capacity" を読み出し full_capacity_ から残量を計算する
        size_t remaining_capacity = 0;
        if (! matchData(remaining_capacity,
                        state_file_.c_str(), "remaining capacity")) {
            return 0;
        }

        size_t percent = min(static_cast<size_t>(100),
                             100 * remaining_capacity / full_capacity_);

        return percent;
    }


    size_t remainingSecond(void) const
    {
        if (! is_available_) {
            return 0;
        }

        if (isCharging()) {
            return 0;
        }

        // "/proc/acpi/battery/BATx/state" ファイルの
        // "present rate" [mW] と "remaining capacity" [mWh] を読み出し、
        // バッテリーが使える時間を計算する
        size_t present_rate = 0;
        if (! matchData(present_rate, state_file_.c_str(), "present rate")) {
            return 0;
        }

        size_t remaining_capacity = 0;
        if (! matchData(remaining_capacity,
                        state_file_.c_str(), "remaining capacity")) {
            return 0;
        }

        size_t second =
            60 * 60 * remaining_capacity / max(present_rate,
                                               static_cast<size_t>(1));
        return second;
    }
};
#endif


BatteryState::BatteryState(void) : pimpl(new pImpl)
{
}


BatteryState::~BatteryState(void)
{
}


bool BatteryState::isAvailable(void) const
{
    return pimpl->isAvailable();
}


bool BatteryState::isCharging(void) const
{
    return pimpl->isCharging();
}


size_t BatteryState::remainingPercent(void) const
{
    return pimpl->remainingPercent();
}


size_t BatteryState::remainingSecond(void) const
{
    return pimpl->remainingSecond();
}
