// -*- C++ -*-
//===------------------------- fuzz_test.cpp ------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

//  A simple program for running regressions on the fuzzing routines.
//  This code is not part of any shipping product.
//
//  To build:
//      clang++ -std=c++11 fuzz_test.cpp fuzzing.cpp
//
//  To use:
//      fuzz_test -r partial_sort [-v] files...
//
//  Each file should contain a test case.

//  TODO: should add some memory tracking, too.


#include <iostream>
#include <fstream>
#include <iterator>
#include <vector>
#include <map>
#include <chrono>

#include "fuzzing.h"

//  ==== Count memory allocations ====

struct MemoryCounters {
    size_t totalAllocationCount;
    size_t netAllocationCount;
    size_t totalBytesAllocated;
    };

MemoryCounters gMemoryCounters;

void ZeroMemoryCounters() {
    gMemoryCounters.totalAllocationCount = 0;
    gMemoryCounters.netAllocationCount = 0;
    gMemoryCounters.totalBytesAllocated = 0;
}

void* operator new(std::size_t size)
{
    if (size == 0) size = 1;
    void *p = ::malloc(size);
    if (p == NULL)
        throw std::bad_alloc();
    gMemoryCounters.totalAllocationCount += 1;
    gMemoryCounters.netAllocationCount  += 1;
    gMemoryCounters.totalBytesAllocated += size;
    return p;
}

void* operator new(std::size_t size, const std::nothrow_t&) noexcept
{
    try { return operator new(size); }
    catch (const std::bad_alloc &) {}
    return nullptr;
}

void* operator new[](std::size_t size)
{
    return ::operator new(size);
}

void* operator new[](std::size_t size, const std::nothrow_t&) noexcept
{
    try { return operator new(size); }
    catch (const std::bad_alloc &) {}
    return nullptr;
}

void  operator delete(void* ptr) noexcept
{
    if (ptr)
        ::free(ptr);
    gMemoryCounters.netAllocationCount -= 1;
}

void  operator delete(void* ptr, const std::nothrow_t&) noexcept
{
    ::operator delete(ptr);
}

void  operator delete[](void* ptr) noexcept
{
    ::operator delete(ptr);
}

void  operator delete[](void* ptr, const std::nothrow_t&) noexcept
{
    ::operator delete(ptr);
}

//  ==== End count memory allocations ====


typedef int (*FuzzProc) (const uint8_t *data, size_t size);

const std::map<std::string, FuzzProc> procs = {
    {"sort",                fuzzing::sort},
    {"stable_sort",         fuzzing::stable_sort},
    {"partition",           fuzzing::partition},
    {"partition_copy",      fuzzing::partition_copy},
    {"stable_partition",    fuzzing::stable_partition},
    {"unique",              fuzzing::unique},
    {"unique_copy",         fuzzing::unique_copy},
    {"nth_element",         fuzzing::nth_element},
    {"partial_sort",        fuzzing::partial_sort},
    {"partial_sort_copy",   fuzzing::partial_sort_copy},
    {"make_heap",           fuzzing::make_heap},
    {"push_heap",           fuzzing::push_heap},
    {"pop_heap",            fuzzing::pop_heap},
    {"regex_ECMAScript",    fuzzing::regex_ECMAScript},
    {"regex_POSIX",         fuzzing::regex_POSIX},
    {"regex_extended",      fuzzing::regex_extended},
    {"regex_awk",           fuzzing::regex_awk},
    {"regex_grep",          fuzzing::regex_grep},
    {"regex_egrep",         fuzzing::regex_egrep},
    {"search",              fuzzing::search}
};



bool verbose = false;

void test_one(const char *filename, FuzzProc fp)
{
    std::vector<uint8_t> v;
    std::ifstream f (filename, std::ios::binary);
    if (!f.is_open())
        std::cerr << "## Can't open '" << filename << "'" << std::endl;
    else
    {
        typedef std::istream_iterator<uint8_t> Iter;
        std::copy(Iter(f), Iter(), std::back_inserter(v));
        if (verbose)
            std::cout << "File '" << filename << "' contains " << v.size() << " entries" << std::endl;
        ZeroMemoryCounters();
        const auto start_time = std::chrono::high_resolution_clock::now();
        int ret = fp (v.data(), v.size());
        const auto finish_time = std::chrono::high_resolution_clock::now();
        MemoryCounters mc = gMemoryCounters;
        if (ret != 0)
            std::cerr << "## Failure code: " << ret << std::endl;
        if (verbose)
        {
            std::cout << "Execution time: "
                << std::chrono::duration_cast<std::chrono::milliseconds>(finish_time - start_time).count()
                << " milliseconds" << std::endl;
            std::cout << "Memory: " 
                      << mc.totalBytesAllocated  << " bytes allocated ("
                      << mc.totalAllocationCount << " allocations); "
                      << mc.netAllocationCount   << " allocations remain" << std::endl;
        }
    }
}

void usage (const char *name)
{
    std::cout << "Usage: " << name << " -r proc [-v] files..." << std::endl;
    std::cout << "Supported routines:" << std::endl;
    for (const auto &p : procs)
        std::cout << "    " << p.first << std::endl;
    std::cout << std::endl;
}

// Poor man's command-line options
const std::string dashR("-r");
const std::string dashV("-v");

int main(int argc, char *argv[])
{
    if (argc < 4 || dashR != argv[1] || procs.find(argv[2]) == procs.end())
        usage(argv[0]);
    else {
        FuzzProc fp = procs.find(argv[2])->second;
        int firstFile = 3;
        if (dashV == argv[firstFile])
        {
            verbose = true;
            ++firstFile;
        }
        for (int i = firstFile; i < argc; ++i)
            test_one(argv[i], fp);
        }
}
