// Copyright 2021 yuyezhong@gmail.com
//
// 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.

#include <cstdio>
#include <iostream>
#include <fstream>
#include <cstdlib>
#include <vector>
#include <regex>
#include "CfigAvbOps.h"
#include "helper.hpp"

std::vector<std::string> splitString(const std::string& subject) {
    if (subject.size() == 0) {
        return std::vector<std::string>();
    }
    static const std::regex re{"\\s+"};
    std::vector<std::string> container {
        std::sregex_token_iterator(subject.begin(), subject.end(), re, -1),
        std::sregex_token_iterator()
    };
    return container;
}

int main(int, char**) {
    int flags = AVB_SLOT_VERIFY_FLAGS_NONE;
    //param 1: preload partitions
    auto preloads = getenv("preloads");
    if (preloads == NULL) {
        preloads = (char*) "";
    }
    std::cout << "[" << __FUNCTION__ << "]: partitions to preload: " << preloads << std::endl;

    //param 2: requested partitions
    auto requests = getenv("requests");
    if (requests == NULL) {
        requests = (char*) "";
    }
    std::cout << "[" << __FUNCTION__ << "]: requested partitions: " << requests << std::endl;
    auto requestVec = splitString(requests);
    //example: const char* requestedPartitions[] = { (const char*) "boot", (const char*)NULL };
    const char* requestedPartitions[requestVec.size() + 1];
    for (int i = 0; i< requestVec.size(); i++) {
        requestedPartitions[i] = requestVec[i].c_str();
        if (requestVec[i] == "recovery") {
            flags |= AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION;
            std::cout << "[" << __FUNCTION__ << "]: using NO-VBMETA mode for recovery" << std::endl;
        }
    }
    requestedPartitions[requestVec.size()] = (const char*)NULL;
    //param3: ab_suffix
    auto abSuffix = getenv("suffix");
    if (abSuffix == NULL) {
        abSuffix = (char*) "";
    }
    std::cout << "[" << __FUNCTION__ << "]: ab_suffix: " << abSuffix << std::endl;

    //main
    auto cfigOps = CfigAvbOps();

    {//preload partitions
        auto preloadVec = splitString(preloads);
        for (auto item: preloadVec) {
            cfigOps.preload_partition(item);
        }
    }

    bool isDeviceUnlocked = false;
    cfigOps.avb_ops_.read_is_device_unlocked(NULL, &isDeviceUnlocked);
    if (isDeviceUnlocked) {
        flags |= AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR;
    }
    std::cout << "[" << __FUNCTION__ << "]: flags: " << flags << std::endl;

    AvbSlotVerifyData *slotData = NULL;
    AvbSlotVerifyResult result = avb_slot_verify(
            &(cfigOps.avb_ops_),
            requestedPartitions,
            abSuffix, /* ab_suffix */
            static_cast<AvbSlotVerifyFlags>(flags),    /* AvbSlotVerifyFlags */
            AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE,
            &slotData);
    if (AVB_SLOT_VERIFY_RESULT_OK == result) {
        auto outFile = "verify_result.json";
        std::cout << "Writing result to " << outFile << "... ";
        std::ofstream outJson(outFile);
        outJson << toString(slotData);
        outJson.close();
        std::cout << " done" << std::endl;
        std::cout << "Run:\n  python -m json.tool " << outFile << std::endl;
    }
    if (slotData) { avb_slot_verify_data_free(slotData); }
    std::cout << "\n\tVerify Result: " << toString(result) << std::endl;
    if (isDeviceUnlocked) {
        switch (result) {
            case AVB_SLOT_VERIFY_RESULT_OK:
                std::cout << "\tVerify Flow: [orange] continue";
                break;
            case AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION:
            case AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED:
            case AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX:
                std::cout << "\tVerify Flow: [orange] allowed errors found: " << toString(result) << std::endl;
                break;
            default:
                std::cout<< "\tVerify Flow: [orange] but fatal errors found" << std::endl;
        }
    } else {
        switch (result) {
            case AVB_SLOT_VERIFY_RESULT_OK:
                std::cout << "\tVerify Flow: [green] continue";
                break;
            default:
                std::cout << "\tVerify Flow: [?????] halt";
        }
    }
    return 0;
}
