/*
 * Copyright (C) 2018 The Android Open Source Project
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
#include "test_utils.h"
#include <fcntl.h>
#include <termios.h>
#include <algorithm>
#include <iterator>
#include <sstream>

namespace fastboot {

namespace {
constexpr int rand_seed = 0;
std::default_random_engine rnd(rand_seed);
}  // namespace

char rand_legal() {
    return rnd() % 128;
}

char rand_illegal() {
    return rand_legal() + 128;
}

char rand_char() {
    return rnd() % 256;
}

int random_int(int start, int end) {
    std::uniform_int_distribution<int> uni(start, end);
    return uni(rnd);
}

std::string RandomString(size_t length, std::function<char(void)> provider) {
    std::string str(length, 0);
    std::generate_n(str.begin(), length, provider);
    return str;
}

std::vector<char> RandomBuf(size_t length, std::function<char(void)> provider) {
    std::vector<char> ret;
    ret.resize(length);
    std::generate_n(ret.begin(), length, provider);
    return ret;
}

std::vector<std::string> SplitBySpace(const std::string& s) {
    std::istringstream iss(s);
    return std::vector<std::string>{std::istream_iterator<std::string>{iss},
                                    std::istream_iterator<std::string>{}};
}

std::vector<std::string> GeneratePartitionNames(const std::string& base, int num_slots) {
    if (!num_slots) {
        return std::vector<std::string>{base};
    }
    std::vector<std::string> ret;
    for (char c = 'a'; c < 'a' + num_slots; c++) {
        ret.push_back(base + '_' + c);
    }

    return ret;
}

std::unordered_map<std::string, std::string> ParseArgs(int argc, char** argv,
                                                       std::string* err_msg) {
    // We ignore any gtest stuff
    std::unordered_map<std::string, std::string> ret;

    for (int i = 1; i < argc; i++) {
        std::string arg(argv[i]);

        const std::string gtest_start("--gtest");
        // We found a non gtest argument
        if (!arg.find("-h") ||
            (!arg.find("--") && arg.find("--gtest") && arg.find("=") != arg.npos)) {
            const std::string start(arg.begin() + 2, arg.begin() + arg.find("="));
            const std::string end(arg.begin() + arg.find("=") + 1, arg.end());
            ret[start] = end;
        } else if (arg.find("--gtest") != 0) {
            *err_msg = android::base::StringPrintf("Illegal argument '%s'\n", arg.c_str());
            return ret;
        }
    }

    return ret;
}

int ConfigureSerial(const std::string& port) {
    int fd = open(port.c_str(), O_RDONLY | O_NOCTTY | O_NONBLOCK);

    if (fd <= 0) {
        return fd;
    }

    struct termios tty;
    tcgetattr(fd, &tty);

    cfsetospeed(&tty, (speed_t)B115200);
    cfsetispeed(&tty, (speed_t)B115200);

    tty.c_cflag &= ~PARENB;
    tty.c_cflag &= ~CSTOPB;
    tty.c_cflag &= ~CSIZE;
    tty.c_cflag |= CS8;

    tty.c_cflag &= ~CRTSCTS;
    tty.c_cc[VMIN] = 0;
    tty.c_cc[VTIME] = 2;
    tty.c_cflag &= ~(ICANON | ECHO | ECHOE | ISIG);

    cfmakeraw(&tty);

    tcflush(fd, TCIFLUSH);
    if (tcsetattr(fd, TCSANOW, &tty) != 0) {
        return -1;
    }

    return fd;
}

int StartProgram(const std::string program, const std::vector<std::string> args, int* rpipe) {
    int link[2];
    if (pipe(link) < 0) {
        return -1;
    }

    pid_t pid = fork();
    if (pid < 0) {  // error
        return -1;
    }

    if (pid) {  // parent
        close(link[1]);
        *rpipe = link[0];
        fcntl(*rpipe, F_SETFL, O_NONBLOCK);  // Non-blocking
    } else {                                 // child
        std::vector<const char*> argv(args.size() + 2, nullptr);
        argv[0] = program.c_str();

        for (int i = 0; i < args.size(); i++) {
            argv[i + 1] = args[i].c_str();
        }

        // We pipe any stderr writes to the parent test process
        dup2(link[1], STDERR_FILENO);  // close stdout and have it now be link[1]
        // Close duplicates
        close(link[0]);
        close(link[1]);

        execvp(program.c_str(), const_cast<char* const*>(argv.data()));
        fprintf(stderr, "Launching validator process '%s' failed with: %s\n", program.c_str(),
                strerror(errno));
        exit(-1);
    }

    return pid;
}

int WaitProgram(const int pid, const int pipe, std::string* error_msg) {
    int status;
    if (waitpid(pid, &status, 0) != pid) {
        close(pipe);
        return -1;
    }
    // Read from pipe
    char buf[1024];
    int n;
    while ((n = read(pipe, buf, sizeof(buf))) > 0) {
        buf[n] = 0; /* terminate the string */
        error_msg->append(buf, n);
    }
    close(pipe);

    if (WIFEXITED(status)) {
        // This WEXITSTATUS macro masks off lower bytes, with no sign extension
        // casting it as a signed char fixes the sign extension issue
        int retmask = WEXITSTATUS(status);
        return reinterpret_cast<int8_t*>(&retmask)[0];
    }

    return -1;
}

}  // namespace fastboot
