/*
 * Copyright (C) 2022 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: BrownoutDetected"

#include <aidl/android/frameworks/stats/IStats.h>
#include <android-base/file.h>
#include <android-base/parseint.h>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <android/binder_manager.h>
#include <hardware/google/pixel/pixelstats/pixelatoms.pb.h>
#include <pixelstats/BrownoutDetectedReporter.h>
#include <time.h>
#include <utils/Log.h>

#include <map>
#include <regex>

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::hardware::google::pixel::PixelAtoms::BrownoutDetected;

#define READING_IDX 2
#define KEY_IDX 0
#define DEFAULT_BATTERY_TEMP 9999999
#define DEFAULT_BATTERY_SOC 100
#define DEFAULT_BATTERY_VOLT 5000000
#define ONE_SECOND_IN_US 1000000

const std::regex kTimestampPattern("^\\S+\\s[0-9]+:[0-9]+:[0-9]+\\S+$");
const std::regex kIrqPattern("^(\\S+)\\striggered\\sat\\s\\S+$");
const std::regex kOdpmPattern("^CH\\d+\\[(\\S+)\\],\\s(\\d+)$");
const std::regex kDvfsPattern("^([A-Z1-9]+):(\\d+)$");
const std::regex kFgPattern("^(voltage_now):(\\d+)$");
const std::regex kBatteryTempPattern("^(battery):(\\d+)$");
const std::regex kBatteryCyclePattern("^(battery_cycle):(\\d+)$");
const std::regex kBatterySocPattern("^(soc):(\\d+)$");
const std::regex kAlreadyUpdatedPattern("^(LASTMEAL_UPDATED)$");

const std::map<std::string, int> kBrownoutReason = {{"uvlo,pmic,if", BrownoutDetected::UVLO_IF},
                                                    {"ocp,pmic,if", BrownoutDetected::OCP_IF},
                                                    {"ocp2,pmic,if", BrownoutDetected::OCP2_IF},
                                                    {"uvlo,pmic,main", BrownoutDetected::UVLO_MAIN},
                                                    {"uvlo,pmic,sub", BrownoutDetected::UVLO_SUB},
                                                    {"ocp,buck1m", BrownoutDetected::OCP_B1M},
                                                    {"ocp,buck2m", BrownoutDetected::OCP_B2M},
                                                    {"ocp,buck3m", BrownoutDetected::OCP_B3M},
                                                    {"ocp,buck4m", BrownoutDetected::OCP_B4M},
                                                    {"ocp,buck5m", BrownoutDetected::OCP_B5M},
                                                    {"ocp,buck6m", BrownoutDetected::OCP_B6M},
                                                    {"ocp,buck7m", BrownoutDetected::OCP_B7M},
                                                    {"ocp,buck8m", BrownoutDetected::OCP_B8M},
                                                    {"ocp,buck9m", BrownoutDetected::OCP_B9M},
                                                    {"ocp,buck10m", BrownoutDetected::OCP_B10M},
                                                    {"ocp,buck1s", BrownoutDetected::OCP_B1S},
                                                    {"ocp,buck2s", BrownoutDetected::OCP_B2S},
                                                    {"ocp,buck3s", BrownoutDetected::OCP_B3S},
                                                    {"ocp,buck4s", BrownoutDetected::OCP_B4S},
                                                    {"ocp,buck5s", BrownoutDetected::OCP_B5S},
                                                    {"ocp,buck6s", BrownoutDetected::OCP_B6S},
                                                    {"ocp,buck7s", BrownoutDetected::OCP_B7S},
                                                    {"ocp,buck8s", BrownoutDetected::OCP_B8S},
                                                    {"ocp,buck9s", BrownoutDetected::OCP_B9S},
                                                    {"ocp,buck10s", BrownoutDetected::OCP_B10S},
                                                    {"ocp,buckas", BrownoutDetected::OCP_BAS},
                                                    {"ocp,buckbs", BrownoutDetected::OCP_BBS},
                                                    {"ocp,buckcs", BrownoutDetected::OCP_BCS},
                                                    {"ocp,buckds", BrownoutDetected::OCP_BDS}};

bool BrownoutDetectedReporter::updateIfFound(std::string line, std::regex pattern,
                                             int *current_value, Update flag) {
    bool found = false;
    std::smatch pattern_match;
    if (std::regex_match(line, pattern_match, pattern)) {
        if (pattern_match.size() < (READING_IDX + 1)) {
            return found;
        }
        found = true;
        int reading = std::stoi(pattern_match[READING_IDX].str());
        if (flag == kUpdateMax) {
            if (*current_value < reading) {
                *current_value = reading;
            }
        } else {
            if (*current_value > reading) {
                *current_value = reading;
            }
        }
    }
    return found;
}

void BrownoutDetectedReporter::setAtomFieldValue(std::vector<VendorAtomValue> *values, int offset,
                                                 int content) {
    std::vector<VendorAtomValue> &val = *values;
    if (offset - kVendorAtomOffset < val.size()) {
        val[offset - kVendorAtomOffset].set<VendorAtomValue::intValue>(content);
    }
}

void BrownoutDetectedReporter::uploadData(const std::shared_ptr<IStats> &stats_client,
                                          const struct BrownoutDetectedInfo max_value) {
    // Load values array
    VendorAtomValue tmp;
    std::vector<VendorAtomValue> values(47);
    setAtomFieldValue(&values, BrownoutDetected::kTriggeredIrqFieldNumber,
                      max_value.triggered_irq_);
    setAtomFieldValue(&values, BrownoutDetected::kTriggeredTimestampFieldNumber,
                      max_value.triggered_timestamp_);
    setAtomFieldValue(&values, BrownoutDetected::kBatteryTempFieldNumber, max_value.battery_temp_);
    setAtomFieldValue(&values, BrownoutDetected::kBatterySocFieldNumber,
                      100 - max_value.battery_soc_);
    setAtomFieldValue(&values, BrownoutDetected::kBatteryCycleFieldNumber,
                      max_value.battery_cycle_);
    setAtomFieldValue(&values, BrownoutDetected::kVoltageNowFieldNumber, max_value.voltage_now_);

    setAtomFieldValue(&values, BrownoutDetected::kOdpmChannel01FieldNumber,
                      max_value.odpm_value_[0]);
    setAtomFieldValue(&values, BrownoutDetected::kOdpmChannel02FieldNumber,
                      max_value.odpm_value_[1]);
    setAtomFieldValue(&values, BrownoutDetected::kOdpmChannel03FieldNumber,
                      max_value.odpm_value_[2]);
    setAtomFieldValue(&values, BrownoutDetected::kOdpmChannel04FieldNumber,
                      max_value.odpm_value_[3]);
    setAtomFieldValue(&values, BrownoutDetected::kOdpmChannel05FieldNumber,
                      max_value.odpm_value_[4]);
    setAtomFieldValue(&values, BrownoutDetected::kOdpmChannel06FieldNumber,
                      max_value.odpm_value_[5]);
    setAtomFieldValue(&values, BrownoutDetected::kOdpmChannel07FieldNumber,
                      max_value.odpm_value_[6]);
    setAtomFieldValue(&values, BrownoutDetected::kOdpmChannel08FieldNumber,
                      max_value.odpm_value_[7]);
    setAtomFieldValue(&values, BrownoutDetected::kOdpmChannel09FieldNumber,
                      max_value.odpm_value_[8]);
    setAtomFieldValue(&values, BrownoutDetected::kOdpmChannel10FieldNumber,
                      max_value.odpm_value_[9]);
    setAtomFieldValue(&values, BrownoutDetected::kOdpmChannel11FieldNumber,
                      max_value.odpm_value_[10]);
    setAtomFieldValue(&values, BrownoutDetected::kOdpmChannel12FieldNumber,
                      max_value.odpm_value_[11]);
    setAtomFieldValue(&values, BrownoutDetected::kOdpmChannel13FieldNumber,
                      max_value.odpm_value_[12]);
    setAtomFieldValue(&values, BrownoutDetected::kOdpmChannel14FieldNumber,
                      max_value.odpm_value_[13]);
    setAtomFieldValue(&values, BrownoutDetected::kOdpmChannel15FieldNumber,
                      max_value.odpm_value_[14]);
    setAtomFieldValue(&values, BrownoutDetected::kOdpmChannel16FieldNumber,
                      max_value.odpm_value_[15]);
    setAtomFieldValue(&values, BrownoutDetected::kOdpmChannel17FieldNumber,
                      max_value.odpm_value_[16]);
    setAtomFieldValue(&values, BrownoutDetected::kOdpmChannel18FieldNumber,
                      max_value.odpm_value_[17]);
    setAtomFieldValue(&values, BrownoutDetected::kOdpmChannel19FieldNumber,
                      max_value.odpm_value_[18]);
    setAtomFieldValue(&values, BrownoutDetected::kOdpmChannel20FieldNumber,
                      max_value.odpm_value_[19]);
    setAtomFieldValue(&values, BrownoutDetected::kOdpmChannel21FieldNumber,
                      max_value.odpm_value_[20]);
    setAtomFieldValue(&values, BrownoutDetected::kOdpmChannel22FieldNumber,
                      max_value.odpm_value_[21]);
    setAtomFieldValue(&values, BrownoutDetected::kOdpmChannel23FieldNumber,
                      max_value.odpm_value_[22]);
    setAtomFieldValue(&values, BrownoutDetected::kOdpmChannel24FieldNumber,
                      max_value.odpm_value_[23]);

    setAtomFieldValue(&values, BrownoutDetected::kDvfsChannel1FieldNumber,
                      max_value.dvfs_value_[0]);
    setAtomFieldValue(&values, BrownoutDetected::kDvfsChannel2FieldNumber,
                      max_value.dvfs_value_[1]);
    setAtomFieldValue(&values, BrownoutDetected::kDvfsChannel3FieldNumber,
                      max_value.dvfs_value_[2]);
    setAtomFieldValue(&values, BrownoutDetected::kDvfsChannel4FieldNumber,
                      max_value.dvfs_value_[3]);
    setAtomFieldValue(&values, BrownoutDetected::kDvfsChannel5FieldNumber,
                      max_value.dvfs_value_[4]);
    setAtomFieldValue(&values, BrownoutDetected::kDvfsChannel6FieldNumber,
                      max_value.dvfs_value_[5]);
    setAtomFieldValue(&values, BrownoutDetected::kBrownoutReasonFieldNumber,
                      max_value.brownout_reason_);

    setAtomFieldValue(&values, BrownoutDetected::kMaxCurrentFieldNumber, max_value.max_curr_);
    setAtomFieldValue(&values, BrownoutDetected::kEvtCntUvlo1FieldNumber, max_value.evt_cnt_uvlo1_);
    setAtomFieldValue(&values, BrownoutDetected::kEvtCntUvlo2FieldNumber, max_value.evt_cnt_uvlo2_);
    setAtomFieldValue(&values, BrownoutDetected::kEvtCntOilo1FieldNumber, max_value.evt_cnt_oilo1_);
    setAtomFieldValue(&values, BrownoutDetected::kEvtCntOilo2FieldNumber, max_value.evt_cnt_oilo2_);
    setAtomFieldValue(&values, BrownoutDetected::kVimonVbattFieldNumber, max_value.vimon_vbatt_);
    setAtomFieldValue(&values, BrownoutDetected::kVimonIbattFieldNumber, max_value.vimon_ibatt_);

    setAtomFieldValue(&values, BrownoutDetected::kMitigationMethod0FieldNumber,
                      max_value.mitigation_method_0_);
    setAtomFieldValue(&values, BrownoutDetected::kMitigationMethod0CountFieldNumber,
                      max_value.mitigation_method_0_count_);
    setAtomFieldValue(&values, BrownoutDetected::kMitigationMethod0TimeUsFieldNumber,
                      max_value.mitigation_method_0_time_us_);

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

long BrownoutDetectedReporter::parseTimestamp(std::string timestamp) {
    struct tm triggeredTimestamp = {};
    std::string timestampFormat = "%Y-%m-%d %H:%M:%S";
    if (strptime(timestamp.substr(0, 19).c_str(), timestampFormat.c_str(), &triggeredTimestamp)) {
        auto logFileTime = std::chrono::system_clock::from_time_t(mktime(&triggeredTimestamp));
        return logFileTime.time_since_epoch().count() / ONE_SECOND_IN_US;
    }
    return 0;
}

int BrownoutDetectedReporter::brownoutReasonCheck(const std::string &brownoutReasonProp) {
    std::string reason = android::base::GetProperty(brownoutReasonProp.c_str(), "");
    if (reason.empty()) {
        // Brownout not found
        return -1;
    }
    auto key = kBrownoutReason.find(reason);
    if (key == kBrownoutReason.end()) {
        return -1;
    }
    return key->second;
}

int parseIRQ(const std::string &element) {
    int idx = atoi(element.c_str());
    if (idx == SMPL_WARN) {
        return BrownoutDetected::SMPL_WARN;
    } else if (idx == UVLO1) {
        return BrownoutDetected::UVLO1;
    } else if (idx == UVLO2) {
        return BrownoutDetected::UVLO2;
    } else if (idx == BATOILO) {
        return BrownoutDetected::BATOILO;
    } else if (idx == BATOILO2) {
        return BrownoutDetected::BATOILO2;
    }
    return -1;
}

void BrownoutDetectedReporter::logBrownoutCsv(const std::shared_ptr<IStats> &stats_client,
                                              const std::string &CsvFilePath,
                                              const std::string &brownoutReasonProp) {
    std::string csvFile;
    if (!android::base::ReadFileToString(CsvFilePath, &csvFile)) {
        return;
    }
    std::istringstream content(csvFile);
    std::string line;
    struct BrownoutDetectedInfo max_value = {};
    max_value.voltage_now_ = DEFAULT_BATTERY_VOLT;
    max_value.battery_soc_ = DEFAULT_BATTERY_SOC;
    max_value.battery_temp_ = DEFAULT_BATTERY_TEMP;
    std::smatch pattern_match;
    max_value.brownout_reason_ = brownoutReasonCheck(brownoutReasonProp);
    if (max_value.brownout_reason_ < 0) {
        return;
    }
    bool isAlreadyUpdated = false;
    std::vector<std::vector<std::string>> rows;
    int row_num = 0;
    while (std::getline(content, line)) {
        if (std::regex_match(line, pattern_match, kAlreadyUpdatedPattern)) {
            isAlreadyUpdated = true;
            break;
        }
        row_num++;
        if (row_num == 1) {
            continue;
        }
        std::vector<std::string> row;
        std::stringstream ss(line);
        std::string field;
        while (getline(ss, field, ',')) {
            row.push_back(field);
        }

        max_value.triggered_timestamp_ = parseTimestamp(row[TIMESTAMP_IDX].c_str());
        max_value.triggered_irq_ = parseIRQ(row[IRQ_IDX]);
        max_value.battery_soc_ = atoi(row[SOC_IDX].c_str());
        max_value.battery_temp_ = atoi(row[TEMP_IDX].c_str());
        max_value.battery_cycle_ = atoi(row[CYCLE_IDX].c_str());
        max_value.voltage_now_ = atoi(row[VOLTAGE_IDX].c_str());
        for (int i = 0; i < DVFS_MAX_IDX; i++) {
            max_value.dvfs_value_[i] = atoi(row[i + DVFS_CHANNEL_0].c_str());
        }
        for (int i = 0; i < ODPM_MAX_IDX; i++) {
            max_value.odpm_value_[i] = atoi(row[i + ODPM_CHANNEL_0].c_str());
        }
        if (row.size() > MAX_CURR) {
            max_value.evt_cnt_oilo1_ = atoi(row[EVT_CNT_IDX_OILO1].c_str());
            max_value.evt_cnt_oilo2_ = atoi(row[EVT_CNT_IDX_OILO2].c_str());
            max_value.evt_cnt_uvlo1_ = atoi(row[EVT_CNT_IDX_UVLO1].c_str());
            max_value.evt_cnt_uvlo2_ = atoi(row[EVT_CNT_IDX_UVLO2].c_str());
            max_value.max_curr_ = atoi(row[MAX_CURR].c_str());
        }
        if (row.size() > IDX_VIMON_I) {
            max_value.vimon_vbatt_ = atoi(row[IDX_VIMON_V].c_str());
            max_value.vimon_ibatt_ = atoi(row[IDX_VIMON_I].c_str());
        }
    }
    if (!isAlreadyUpdated && max_value.battery_temp_ != DEFAULT_BATTERY_TEMP) {
        std::string file_content = "LASTMEAL_UPDATED\n" + csvFile;
        android::base::WriteStringToFile(file_content, CsvFilePath);
        uploadData(stats_client, max_value);
    }
}

void BrownoutDetectedReporter::logBrownout(const std::shared_ptr<IStats> &stats_client,
                                           const std::string &logFilePath,
                                           const std::string &brownoutReasonProp) {
    std::string logFile;
    if (!android::base::ReadFileToString(logFilePath, &logFile)) {
        return;
    }
    std::istringstream content(logFile);
    std::string line;
    struct BrownoutDetectedInfo max_value = {};
    max_value.voltage_now_ = DEFAULT_BATTERY_VOLT;
    max_value.battery_soc_ = DEFAULT_BATTERY_SOC;
    max_value.battery_temp_ = DEFAULT_BATTERY_TEMP;
    std::smatch pattern_match;
    int odpm_index = 0, dvfs_index = 0;
    max_value.brownout_reason_ = brownoutReasonCheck(brownoutReasonProp);
    if (max_value.brownout_reason_ < 0) {
        return;
    }
    bool isAlreadyUpdated = false;
    while (std::getline(content, line)) {
        if (std::regex_match(line, pattern_match, kAlreadyUpdatedPattern)) {
            isAlreadyUpdated = true;
            break;
        }
        if (std::regex_match(line, pattern_match, kIrqPattern)) {
            if (pattern_match.size() < (KEY_IDX + 1)) {
                return;
            }
            std::ssub_match irq = pattern_match[KEY_IDX];
            if (irq.str().find("batoilo") != std::string::npos) {
                max_value.triggered_irq_ = BrownoutDetected::BATOILO;
                continue;
            }
            if (irq.str().find("vdroop1") != std::string::npos) {
                max_value.triggered_irq_ = BrownoutDetected::UVLO1;
                continue;
            }
            if (irq.str().find("vdroop2") != std::string::npos) {
                max_value.triggered_irq_ = BrownoutDetected::UVLO2;
                continue;
            }
            if (irq.str().find("smpl_gm") != std::string::npos) {
                max_value.triggered_irq_ = BrownoutDetected::SMPL_WARN;
                continue;
            }
            continue;
        }
        if (std::regex_match(line, pattern_match, kTimestampPattern)) {
            max_value.triggered_timestamp_ = parseTimestamp(line.c_str());
            continue;
        }
        if (updateIfFound(line, kBatterySocPattern, &max_value.battery_soc_, kUpdateMin)) {
            continue;
        }
        if (updateIfFound(line, kBatteryTempPattern, &max_value.battery_temp_, kUpdateMin)) {
            continue;
        }
        if (updateIfFound(line, kBatteryCyclePattern, &max_value.battery_cycle_, kUpdateMax)) {
            continue;
        }
        if (updateIfFound(line, kFgPattern, &max_value.voltage_now_, kUpdateMin)) {
            continue;
        }
        if (updateIfFound(line, kDvfsPattern, &max_value.dvfs_value_[dvfs_index], kUpdateMax)) {
            dvfs_index++;
            // Discarding previous value and update with new DVFS value
            if (dvfs_index == DVFS_MAX_IDX) {
                dvfs_index = 0;
            }
            continue;
        }
        if (updateIfFound(line, kOdpmPattern, &max_value.odpm_value_[odpm_index], kUpdateMax)) {
            odpm_index++;
            // Discarding previous value and update with new ODPM value
            if (odpm_index == ODPM_MAX_IDX) {
                odpm_index = 0;
            }
            continue;
        }
    }
    if (!isAlreadyUpdated && max_value.battery_temp_ != DEFAULT_BATTERY_TEMP) {
        std::string file_content = "LASTMEAL_UPDATED\n" + logFile;
        android::base::WriteStringToFile(file_content, logFilePath);
        uploadData(stats_client, max_value);
    }
}

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