/*
 * Copyright (C) 2021 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.
 */

#define LOG_TAG "pixelstats-uevent: ChargeStatsReporter"

#include <android-base/file.h>
#include <hardware/google/pixel/pixelstats/pixelatoms.pb.h>
#include <log/log.h>
#include <pixelstats/ChargeStatsReporter.h>
#include <pixelstats/StatsHelper.h>
#include <time.h>
#include <utils/Timers.h>

#include <cmath>

namespace android {
namespace hardware {
namespace google {
namespace pixel {

using aidl::android::frameworks::stats::IStats;
using aidl::android::frameworks::stats::VendorAtom;
using aidl::android::frameworks::stats::VendorAtomValue;
using android::base::ReadFileToString;
using android::base::WriteStringToFile;
using android::hardware::google::pixel::PixelAtoms::ChargeStats;
using android::hardware::google::pixel::PixelAtoms::VoltageTierStats;

#define DURATION_FILTER_SECS 15
#define CHG_STATS_FMT0 "%d,%d,%d, %d,%d,%d,%d"
#define CHG_STATS_FMT1 "%d,%d,%d, %d,%d,%d,%d %d" /* AACR */
#define CHG_STATS_FMT2 "%d,%d,%d, %d,%d,%d,%d %d %d,%d" /* AACR + CSI */

ChargeStatsReporter::ChargeStatsReporter() {}

int64_t ChargeStatsReporter::getTimeSecs(void) {
    return nanoseconds_to_seconds(systemTime(SYSTEM_TIME_BOOTTIME));
}

void ChargeStatsReporter::ReportChargeStats(const std::shared_ptr<IStats> &stats_client,
                                            const std::string line, const std::string wline_at,
                                            const std::string wline_ac,
                                            const std::string pca_line) {
    int charge_stats_fields[] = {
            ChargeStats::kAdapterTypeFieldNumber,
            ChargeStats::kAdapterVoltageFieldNumber,
            ChargeStats::kAdapterAmperageFieldNumber,
            ChargeStats::kSsocInFieldNumber,
            ChargeStats::kVoltageInFieldNumber,
            ChargeStats::kSsocOutFieldNumber,
            ChargeStats::kVoltageOutFieldNumber,
            ChargeStats::kChargeCapacityFieldNumber,
            ChargeStats::kCsiAggregateStatusFieldNumber,
            ChargeStats::kCsiAggregateTypeFieldNumber,
            ChargeStats::kAdapterCapabilities0FieldNumber,
            ChargeStats::kAdapterCapabilities1FieldNumber,
            ChargeStats::kAdapterCapabilities2FieldNumber,
            ChargeStats::kAdapterCapabilities3FieldNumber,
            ChargeStats::kAdapterCapabilities4FieldNumber,
            ChargeStats::kReceiverState0FieldNumber,
            ChargeStats::kReceiverState1FieldNumber,
    };
    const int32_t chg_fields_size = std::size(charge_stats_fields);
    static_assert(chg_fields_size == 17, "Unexpected charge stats fields size");
    const int32_t wlc_fields_size = 7;
    std::vector<VendorAtomValue> values(chg_fields_size);
    VendorAtomValue val;
    int32_t i = 0, tmp[chg_fields_size] = {0}, fields_size = (chg_fields_size - wlc_fields_size);
    int32_t pca_ac[2] = {0}, pca_rs[5] = {0};
    std::string pdo_line, file_contents;
    std::istringstream ss;

    ALOGD("processing %s", line.c_str());
    if (sscanf(line.c_str(), CHG_STATS_FMT2, &tmp[0], &tmp[1], &tmp[2], &tmp[3], &tmp[4], &tmp[5],
               &tmp[6], &tmp[7], &tmp[8], &tmp[9]) == 10) {
        /*
         * Charging Speed Indicator (CSI) the sum of the reasons that limit the charging speed in
         * this charging session.
         */
    } else if (sscanf(line.c_str(), CHG_STATS_FMT1, &tmp[0], &tmp[1], &tmp[2], &tmp[3], &tmp[4],
               &tmp[5], &tmp[6], &tmp[7]) == 8) {
        /*
         * Age Adjusted Charge Rate (AACR) logs an additional battery capacity in order to determine
         * the charge curve needed to minimize battery cycle life degradation, while also minimizing
         * impact to the user.
         */
    } else if (sscanf(line.c_str(), CHG_STATS_FMT0, &tmp[0], &tmp[1], &tmp[2], &tmp[3], &tmp[4],
                      &tmp[5], &tmp[6]) != 7) {
        ALOGE("Couldn't process %s", line.c_str());
        return;
    }

    if (!wline_at.empty()) {
        int32_t ssoc_tmp = 0;
        ALOGD("wlc: processing %s", wline_at.c_str());
        if (sscanf(wline_at.c_str(), "A:%d", &ssoc_tmp) != 1) {
            ALOGE("Couldn't process %s", wline_at.c_str());
        } else {
            tmp[0] = wireless_charge_stats_.TranslateSysModeToAtomValue(ssoc_tmp);
            ALOGD("wlc: processing %s", wline_ac.c_str());
            if (sscanf(wline_ac.c_str(), "D:%x,%x,%x,%x,%x, %x,%x", &tmp[10], &tmp[11], &tmp[12],
                       &tmp[13], &tmp[14], &tmp[15], &tmp[16]) != 7)
                ALOGE("Couldn't process %s", wline_ac.c_str());
            else
                fields_size = chg_fields_size; /* include wlc stats */
        }
    }

    if (!pca_line.empty()) {
        ALOGD("pca: processing %s", pca_line.c_str());
        if (sscanf(pca_line.c_str(), "D:%x,%x %x,%x,%x,%x,%x", &pca_ac[0], &pca_ac[1], &pca_rs[0],
                   &pca_rs[1], &pca_rs[2], &pca_rs[3], &pca_rs[4]) != 7) {
            ALOGE("Couldn't process %s", pca_line.c_str());
        } else {
            fields_size = chg_fields_size; /* include pca stats */
            tmp[12] = pca_rs[2];
            tmp[13] = pca_rs[3];
            tmp[14] = pca_rs[4];
            tmp[16] = pca_rs[1];
            if (wline_at.empty()) {
                /* force adapter type to PPS when pca log is available, but not wlc */
                tmp[0] = PixelAtoms::ChargeStats::ADAPTER_TYPE_USB_PD_PPS;
                tmp[10] = pca_ac[0];
                tmp[11] = pca_ac[1];
                tmp[15] = pca_rs[0];
            }
        }
    }

    if (ReadFileToString(kGChargerMetricsPath.c_str(), &file_contents)) {
        ss.str(file_contents);
        while (std::getline(ss, pdo_line)) {
            if (sscanf(pdo_line.c_str(), "D:%x,%x,%x,%x,%x,%x,%x", &pca_ac[0], &pca_ac[1], &pca_rs[0],
                   &pca_rs[1], &pca_rs[2], &pca_rs[3], &pca_rs[4]) != 7) {
                continue;
            } else {
                ALOGD("processed %s, apdo:%d, pdo:%d", pdo_line.c_str(), pca_ac[1], pca_rs[4]);
                tmp[15] = pca_ac[1]; /* APDO */
                tmp[16] = pca_rs[4]; /* PDO */
                break;
            }
        }
    }

    for (i = 0; i < fields_size; i++) {
        val.set<VendorAtomValue::intValue>(tmp[i]);
        values[charge_stats_fields[i] - kVendorAtomOffset] = val;
    }

    VendorAtom event = {.reverseDomainName = "",
                        .atomId = PixelAtoms::Atom::kChargeStats,
                        .values = std::move(values)};
    const ndk::ScopedAStatus ret = stats_client->reportVendorAtom(event);
    if (!ret.isOk())
        ALOGE("Unable to report ChargeStats to Stats service");
}

void ChargeStatsReporter::ReportVoltageTierStats(const std::shared_ptr<IStats> &stats_client,
                                                 const char *line, const bool has_wireless = false,
                                                 const std::string &wfile_contents = "") {
    int voltage_tier_stats_fields[] = {
            VoltageTierStats::kVoltageTierFieldNumber,
            VoltageTierStats::kSocInFieldNumber, /* retrieved via ssoc_tmp */
            VoltageTierStats::kCcInFieldNumber,
            VoltageTierStats::kTempInFieldNumber,
            VoltageTierStats::kTimeFastSecsFieldNumber,
            VoltageTierStats::kTimeTaperSecsFieldNumber,
            VoltageTierStats::kTimeOtherSecsFieldNumber,
            VoltageTierStats::kTempMinFieldNumber,
            VoltageTierStats::kTempAvgFieldNumber,
            VoltageTierStats::kTempMaxFieldNumber,
            VoltageTierStats::kIbattMinFieldNumber,
            VoltageTierStats::kIbattAvgFieldNumber,
            VoltageTierStats::kIbattMaxFieldNumber,
            VoltageTierStats::kIclMinFieldNumber,
            VoltageTierStats::kIclAvgFieldNumber,
            VoltageTierStats::kIclMaxFieldNumber,
            VoltageTierStats::kMinAdapterPowerOutFieldNumber,
            VoltageTierStats::kTimeAvgAdapterPowerOutFieldNumber,
            VoltageTierStats::kMaxAdapterPowerOutFieldNumber,
            VoltageTierStats::kChargingOperatingPointFieldNumber};

    const int32_t vtier_fields_size = std::size(voltage_tier_stats_fields);
    static_assert(vtier_fields_size == 20, "Unexpected voltage tier stats fields size");
    const int32_t wlc_fields_size = 4;
    std::vector<VendorAtomValue> values(vtier_fields_size);
    VendorAtomValue val;
    float ssoc_tmp;
    int32_t i = 0, tmp[vtier_fields_size - 1] = {0}, /* ssoc_tmp is not saved in this array */
            fields_size = (vtier_fields_size - wlc_fields_size);

    if (sscanf(line, "%d, %f,%d,%d, %d,%d,%d, %d,%d,%d, %d,%d,%d, %d,%d,%d", &tmp[0], &ssoc_tmp,
               &tmp[1], &tmp[2], &tmp[3], &tmp[4], &tmp[5], &tmp[6], &tmp[7], &tmp[8], &tmp[9],
               &tmp[10], &tmp[11], &tmp[12], &tmp[13], &tmp[14]) != 16) {
        /* If format isn't as expected, then ignore line on purpose */
        return;
    }

    if (has_wireless) {
        wireless_charge_stats_.CalculateWirelessChargeStats(static_cast<int>(ssoc_tmp),
                                                            wfile_contents);
        tmp[15] = wireless_charge_stats_.pout_min_;
        tmp[16] = wireless_charge_stats_.pout_avg_;
        tmp[17] = wireless_charge_stats_.pout_max_;
        tmp[18] = wireless_charge_stats_.of_freq_;
        fields_size = vtier_fields_size; /* include wlc stats */
    }

    ALOGD("VoltageTierStats: processed %s", line);
    val.set<VendorAtomValue::intValue>(tmp[0]);
    values[voltage_tier_stats_fields[0] - kVendorAtomOffset] = val;
    val.set<VendorAtomValue::floatValue>(ssoc_tmp);
    values[voltage_tier_stats_fields[1] - kVendorAtomOffset] = val;
    for (i = 2; i < fields_size; i++) {
        val.set<VendorAtomValue::intValue>(tmp[i - 1]);
        values[voltage_tier_stats_fields[i] - kVendorAtomOffset] = val;
    }

    VendorAtom event = {.reverseDomainName = "",
                        .atomId = PixelAtoms::Atom::kVoltageTierStats,
                        .values = std::move(values)};
    const ndk::ScopedAStatus ret = stats_client->reportVendorAtom(event);
    if (!ret.isOk())
        ALOGE("Unable to report VoltageTierStats to Stats service");
}

/**
 * b/223664185
 * Adds a rolling window filter to charge stats. If the time has expired, there will be a new log
 * event.
 *
 * This helps ensure that we throttle stats even if there is an intermittent disconnect, while still
 * retaining some stats on the disconnect.
 */
bool ChargeStatsReporter::shouldReportEvent(void) {
    const int64_t current_time = getTimeSecs();
    if (current_time == 0) {
        ALOGE("Current boot time is zero!");
        return false;
    }

    if (log_event_time_secs_ == 0 || log_event_time_secs_ + DURATION_FILTER_SECS < current_time) {
        log_event_time_secs_ = current_time;
        return true;
    }

    return false;
}

void ChargeStatsReporter::checkAndReport(const std::shared_ptr<IStats> &stats_client,
                                         const std::string &path) {
    std::string file_contents, line, wfile_contents, wline_at, wline_ac, pca_file_contents,
            pca_line, thermal_file_contents, gcharger_file_contents, gdbatt_file_contents;
    std::istringstream ss;
    bool has_wireless, has_pca, has_thermal, has_gcharger, has_dual_batt;

    if (!ReadFileToString(path.c_str(), &file_contents)) {
        ALOGE("Unable to read %s - %s", path.c_str(), strerror(errno));
        return;
    }

    ss.str(file_contents);

    if (!std::getline(ss, line)) {
        ALOGE("Unable to read first line");
        return;
    }

    if (!WriteStringToFile("0", path.c_str())) {
        ALOGE("Couldn't clear %s - %s", path.c_str(), strerror(errno));
    }

    if (!shouldReportEvent()) {
        ALOGW("Too many log events; event ignored.");
        return;
    }

    has_pca = pca_charge_stats_.CheckPcaContentsAndAck(&pca_file_contents);
    if (has_pca) {
        std::istringstream pca_ss;

        pca_ss.str(pca_file_contents);
        std::getline(pca_ss, pca_line);
    }

    has_wireless = wireless_charge_stats_.CheckWirelessContentsAndAck(&wfile_contents);
    if (has_wireless) {
        std::istringstream wss;

        /* there are two lines in the head, A: ...(Adapter Type) and D: ...(Adapter Capabilities) */
        wss.str(wfile_contents);
        std::getline(wss, wline_at);
        std::getline(wss, wline_ac);

        /* reset initial tier soc */
        wireless_charge_stats_.tier_soc_ = 0;
    }

    ReportChargeStats(stats_client, line, wline_at, wline_ac, pca_line);

    while (std::getline(ss, line)) {
        ReportVoltageTierStats(stats_client, line.c_str(), has_wireless, wfile_contents);
    }

    has_thermal = checkContentsAndAck(&thermal_file_contents, kThermalChargeMetricsPath);
    if (has_thermal) {
        std::istringstream wss;
        wss.str(thermal_file_contents);
        while (std::getline(wss, line)) {
            ReportVoltageTierStats(stats_client, line.c_str());
        }
    }

    has_gcharger = checkContentsAndAck(&gcharger_file_contents, kGChargerMetricsPath);
    if (has_gcharger) {
        std::istringstream wss;
        wss.str(gcharger_file_contents);
        while (std::getline(wss, line)) {
            ReportVoltageTierStats(stats_client, line.c_str());
        }
    }

    has_dual_batt = checkContentsAndAck(&gdbatt_file_contents, kGDualBattMetricsPath);
    if (has_dual_batt) {
        std::istringstream wss;
        wss.str(gdbatt_file_contents);
        while (std::getline(wss, line)) {
            ReportVoltageTierStats(stats_client, line.c_str());
        }
    }
}

bool ChargeStatsReporter::checkContentsAndAck(std::string *file_contents, const std::string &path) {
    if (!ReadFileToString(path.c_str(), file_contents)) {
        return false;
    }

    if (!WriteStringToFile("0", path.c_str())) {
        ALOGE("Couldn't clear %s - %s", path.c_str(), strerror(errno));
        return false;
    }
    return true;
}

}  // namespace pixel
}  // namespace google
}  // namespace hardware
}  // namespace android
