#include "cptest.h"

#include <iostream>
#include <string>
#include <vector>
#include <fcntl.h>

#include "gtest/gtest.h"
#include "debug.h"

using namespace std;

void
cptestPrepareToDie()
{
    Debug::setNewDefaultStdout(&std::cerr);            // EXPECT_DEATH checks cerror for messages, so send them there.
}

CPTestTempfile::CPTestTempfile(const std::vector<std::string> &lines)
{
    // Create the file
    char temp_file_template[] = "/tmp/cptest_temp_file_XXXXXX";
    int fd = mkstemp(temp_file_template);
    if (fd < 0) {
        // LCOV_EXCL_START - mkstemp rarely fails, can't cause it.
        ADD_FAILURE() << "Failed to open tempfile";
        return;
        // LCOV_EXCL_STOP
    }
    fname =  temp_file_template;        // Template was modified to actual file name

    // Fill it with the requested lines
    for (const auto &l : lines) {
        EXPECT_EQ((unsigned long)write(fd, l.c_str(), l.size()), l.size());
        EXPECT_EQ(write(fd, "\n", 1), 1);
    }

    close(fd);
}

CPTestTempfile::CPTestTempfile()
        :
    CPTestTempfile(std::vector<std::string>{})
{
}

CPTestTempfile::~CPTestTempfile()
{
    if (!fname.empty()) {
        unlink(fname.c_str());
    }
}

string
CPTestTempfile::readFile() const
{
    int fd = open(fname.c_str(), 0);
    EXPECT_NE(fd, -1);

    string result;
    char buf[100];
    while (true) {
        auto bytes_read = read(fd, buf, 100);
        if (bytes_read <= 0) break;
        result += string(buf, bytes_read);
    }
    return result;
}

// Parse Hex data, e.g. what's generated by "tcpdump -xx", into a vector
vector<u_char>
cptestParseHex(const string &hex_text)
{
    vector<u_char> v;

    // Use stringstream and istream_iterator to break the input into whitespace separated strings
    stringstream str(hex_text);
    for (auto it = istream_iterator<string>(str); it != istream_iterator<string>(); it++) {
        const string &t = *it;
        size_t l = t.size();
        if (l==0) continue;
        if (t[l-1]==':') continue;    // tcpdump uses xxxx: to mark offsets, not data. So ignore it.
        dbgAssert(t.size() %2 == 0)
            << AlertInfo(AlertTeam::CORE, "testing")
            << "Expecting an even number of hex digits, "
            << t
            << " is invalid";
        for (uint i=0; i<t.size()/2; i++) {
            u_char n = strtoul(t.substr(i*2, 2).c_str(), nullptr, 16);
            v.push_back(n);
        }
    }

    return v;
}

// The inverse of cptest_parse_hex
// Take a vector of data, and generate hex from it output, like tcpdump.
std::string
cptestGenerateHex(const std::vector<u_char> &vec, bool print_offsets)
{
    std::string res;

    int total_hex_groups_emitted = 0;
    int num_hex_groups_emitted_in_cur_line = 0;
    for (size_t i = 0; i<vec.size(); i++) {
        if (num_hex_groups_emitted_in_cur_line == 16) {
            res += "\n";
            num_hex_groups_emitted_in_cur_line = 0;
        }
        if (print_offsets && (num_hex_groups_emitted_in_cur_line == 0)) {
            char offset_str[12];
            snprintf(offset_str, sizeof(offset_str), "%04x:  ", total_hex_groups_emitted);
            res += offset_str;
        }

        char cur_char_as_hex[10];
        snprintf(cur_char_as_hex, sizeof(cur_char_as_hex), "%02x ", vec[i]);
        res += cur_char_as_hex;
        num_hex_groups_emitted_in_cur_line++;
        total_hex_groups_emitted++;
    }

    return res;
}

// Get the path to a file, which is in the same directory as the binary.
std::string
cptestFnameInExeDir(const std::string &name)
{
    auto bin_path = ::testing::internal::GetArgvs()[0];    // Internal ugly API.
    auto slash = bin_path.rfind('/');
    if (slash==string::npos) {
        // bin_path contains no dir. So return name with no dir
        // LCOV_EXCL_START - Our unit tests always run from an absolute path
        return name;
        // LCOV_EXCL_STOP
    }
    auto bin_dir = bin_path.substr(0, slash);
    return bin_dir + "/" + name;
}

string
cptestFnameInSrcDir(const string &name)
{
    char *path = getenv("CURR_SRC_DIR");
    if (path == nullptr) return name;
    return std::string(path) + "/" + name;
}
