/*
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * 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 <algorithm>
#include <cctype>
#include <chrono>
#include <cstring>
#include <iostream>
#include <random>
#include <string>
#include <thread>
#include <vector>

#include <sys/mman.h>
#include <sys/syscall.h>
#include <unistd.h>

using namespace std::chrono;
using namespace std::chrono_literals;

namespace {
#define USED_FUNCTION __attribute__((__used__)) __attribute__((optnone))
constexpr milliseconds eachStackFunRunTime = 100ms;
constexpr milliseconds msDuartion = 1000ms;
const ssize_t ERRINFOLEN = 512;

extern USED_FUNCTION void LoopBranch0(std::default_random_engine &rnd, int level);
extern USED_FUNCTION void LoopBranch1(std::default_random_engine &rnd, int level);

struct Option {
    int numThreads {5};
    int second {36000};
    int stack {5};
    bool noWait = false;
    bool dynamicStack = false;
    bool mmap = false;
    bool iowait = false;
    bool branch = false;
    bool nonew = false;
    bool nofunc = false;
    int boundCpu {-1};
    int sleepms {0};
};

inline int GetTid()
{
    int res = static_cast<int>(syscall(SYS_gettid));
    return res;
}

USED_FUNCTION void LoopDummy(milliseconds anything)
{
    if (anything.count() > 0) {
        printf("");
    }
}

USED_FUNCTION void LoopBranch0(std::default_random_engine &rnd, int level)
{
    constexpr int two {2};
    if (level == 0) {
        printf("");
        return;
    }
    if (rnd() % two == 0) {
        LoopBranch0(rnd, --level);
    } else {
        LoopBranch1(rnd, --level);
    }
}

USED_FUNCTION void LoopBranch1(std::default_random_engine &rnd, int level)
{
    constexpr int two {2};
    if (level == 0) {
        printf("");
        return;
    }
    if (rnd() % two == 0) {
        LoopBranch0(rnd, --level);
    } else {
        LoopBranch1(rnd, --level);
    }
}

USED_FUNCTION void LoopBranch()
{
    constexpr int two {2};
    int branchLevel = 10;
    std::default_random_engine rnd;
    if (rnd() % two == 0) {
        LoopBranch0(rnd, branchLevel);
    } else {
        LoopBranch1(rnd, branchLevel);
    }
}

USED_FUNCTION void LoopIowait()
{
    std::default_random_engine rnd;
    FILE *fp = fopen("temp", "rw");
    if (fp == nullptr) {
        return;
    }
    
    std::unique_ptr<FILE, decltype(&fclose)> fd {fp, &fclose};
    if (fd != nullptr) {
        const std::string tempBuf = std::to_string(rnd());
        fwrite(tempBuf.c_str(), tempBuf.size(), 1, fd.get());
    }
}

USED_FUNCTION void LoopMmap()
{
    int *arr = static_cast<int *>(
        mmap(nullptr, getpagesize(), PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, 0, 0));

    int *ptr = arr;
    int someVal1 {10};
    int someVal2 {20};
    int someVal3 {30};
    *ptr = someVal1;
    ++ptr;
    *ptr = someVal2;
    ++ptr;
    *ptr = someVal3;
    munmap(arr, getpagesize());
    return;
}

USED_FUNCTION void LoopFunction(milliseconds timeOutMS, const Option &option)
{
    auto now = std::chrono::steady_clock::now();
    auto sleepTime = now + seconds(1);
    int count = 0;
    while (std::chrono::steady_clock::now() < (now + timeOutMS)) {
        if (option.sleepms > 0) {
            if (std::chrono::steady_clock::now() >= sleepTime) {
                sleepTime = std::chrono::steady_clock::now() + seconds(1);
                std::this_thread::sleep_for(std::chrono::milliseconds(option.sleepms));
            }
        }
        if (option.mmap) {
            LoopMmap();
        }
        if (option.iowait) {
            LoopIowait();
        }
        if (option.branch) {
            LoopBranch();
        }

        std::default_random_engine rnd;
        int a = rnd();
        int b = rnd();
        int c = rnd();
        a = (a++) * (b++) * (c++);

        if (a == 0) {
            continue;
        }

        if (!option.nonew) {
            auto p = new unsigned int;
            *p = count++;
            delete p;
            p = nullptr;
        }

        if (!option.nofunc) {
            LoopDummy(timeOutMS);
        }
    }
    return;
}

inline void Loop(milliseconds timeOutMS, const Option &option)
{
    printf("loop at %s\n", __func__);
    LoopFunction(timeOutMS, option);
    return;
}

USED_FUNCTION void CallStack10(int currentStack, const Option &option)
{
    if (option.stack > 0) {
        Loop(option.second * msDuartion, option);
    }
}

USED_FUNCTION void CallStack9(int currentStack, const Option &option)
{
    if (option.stack > 0) {
        if (option.dynamicStack) {
            Loop(eachStackFunRunTime, option); // loop 100 ms
        }
        CallStack10(currentStack - 1, option);
    } else {
        Loop(option.second * msDuartion, option);
    }
}

USED_FUNCTION void CallStack8(int currentStack, const Option &option)
{
    if (option.stack > 0) {
        if (option.dynamicStack) {
            Loop(eachStackFunRunTime, option); // loop 100 ms
        }
        CallStack9(currentStack - 1, option);
    } else {
        Loop(option.second * msDuartion, option);
    }
}

USED_FUNCTION void CallStack7(int currentStack, const Option &option)
{
    if (option.stack > 0) {
        if (option.dynamicStack) {
            Loop(eachStackFunRunTime, option); // loop 100 ms
        }
        CallStack8(currentStack - 1, option);
    } else {
        Loop(option.second * msDuartion, option);
    }
}

USED_FUNCTION void CallStack6(int currentStack, const Option &option)
{
    if (option.stack > 0) {
        if (option.dynamicStack) {
            Loop(eachStackFunRunTime, option); // loop 100 ms
        }
        CallStack7(currentStack - 1, option);
    } else {
        Loop(option.second * msDuartion, option);
    }
}

USED_FUNCTION void CallStack5(int currentStack, const Option &option)
{
    if (option.stack > 0) {
        if (option.dynamicStack) {
            Loop(eachStackFunRunTime, option); // loop 100 ms
        }
        CallStack6(currentStack - 1, option);
    } else {
        Loop(option.second * msDuartion, option);
    }
}

USED_FUNCTION void CallStack4(int currentStack, const Option &option)
{
    if (option.stack > 0) {
        if (option.dynamicStack) {
            Loop(eachStackFunRunTime, option); // loop 100 ms
        }
        CallStack5(currentStack - 1, option);
    } else {
        Loop(option.second * msDuartion, option);
    }
}

USED_FUNCTION void CallStack3(int currentStack, const Option &option)
{
    if (option.stack > 0) {
        if (option.dynamicStack) {
            Loop(eachStackFunRunTime, option); // loop 100 ms
        }
        CallStack4(currentStack - 1, option);
    } else {
        Loop(option.second * msDuartion, option);
    }
}

USED_FUNCTION void CallStack2(int currentStack, const Option &option)
{
    if (option.stack > 0) {
        if (option.dynamicStack) {
            Loop(eachStackFunRunTime, option); // loop 100 ms
        }
        CallStack3(currentStack - 1, option);
    } else {
        Loop(option.second * msDuartion, option);
    }
}

USED_FUNCTION void CallStack1(int currentStack, const Option &option)
{
    if (option.stack > 0) {
        if (option.dynamicStack) {
            Loop(eachStackFunRunTime, option); // loop 100 ms
        }
        CallStack2(currentStack - 1, option);
    } else {
        Loop(option.second * msDuartion, option);
    }
}

USED_FUNCTION void CallStack0(int currentStack, const Option &option)
{
    if (option.stack > 0) {
        if (option.dynamicStack) {
            Loop(eachStackFunRunTime, option); // loop 100 ms
        }
        CallStack1(currentStack - 1, option);
    } else {
        Loop(option.second * msDuartion, option);
    }
}

USED_FUNCTION void ExampleThread(const Option &option)
{
    printf("thread %d ++ with %d %d \n", GetTid(), option.second, option.stack);
    CallStack0(option.stack, option);
    printf("thread %d --\n", GetTid());

    return;
}

USED_FUNCTION void RunSampleThread(const Option &option)
{
    printf("run %d threads for %d second with %d stack level\n", option.numThreads, option.second,
           option.stack);
    printf("main thread %d\n", GetTid());

    std::thread threads[option.numThreads];
    for (int count = 0; count < option.numThreads; ++count) {
        threads[count] = std::thread(ExampleThread, option);
    }
    for (int count = 0; count < option.numThreads; ++count) {
        threads[count].join();
    }
    printf("all thread exit\n");
}

void WaitStart()
{
    std::string startArg;
    std::cout << "Please input 'start' to begin the subthread: \n";
    while (true) {
        std::cin >> startArg;
        if (startArg.compare("start") == 0) {
            break;
        } else {
            continue;
        }
    }
}

void Help()
{
    printf("this is a demo test command\n");
    printf("  Use the following commands to simulate different scenarios\n");
    printf("  --help\n");
    printf("    this page\n");
    printf("  --thread <number>\n");
    printf("    setup the thread number, default is 5 second\n");
    printf("  --time <time>\n");
    printf("    setup run sec, default is 36000 second\n");
    printf("  --stack <level>\n");
    printf("    setup stack level, default is 5\n");
    printf("  --nowait\n");
    printf("    setup skip the start, default wait the start\n");
    printf("  --dynamic\n");
    printf("    will run some code in each stack level\n");
    printf("  --mmap\n");
    printf("    will run mmap code in the loop\n");
    printf("  --iowait\n");
    printf("    will run iowait code in the loop\n");
    printf("  --branch\n");
    printf("    will run branch code in the loop\n");
    printf("  --nonew\n");
    printf("    will not new memory in the loop\n");
    printf("  --nofunc\n");
    printf("    will not call dummy func in the loop\n");
    printf("  --boundcpu <cpu>\n");
    printf("    the process will bound to <cpu>\n");
    printf("  --sleep <milliseconds>\n");
    printf("    threads will sleep <milliseconds> per second, default is 0.\n");
}

bool GetIntFromArg(std::vector<std::string> &args, int &value)
{
    if (!args.empty()) {
        if (std::all_of(args.begin()->begin(), args.begin()->end(), ::isdigit)) {
            value = std::stoi(args[0]);
            args.erase(args.begin());
        } else {
            printf("unknown format '%s'\n", args[0].c_str());
            return false;
        }
    }
    return true;
}

bool MatchArgs(std::vector<std::string> &args, const std::string &option)
{
    if (args[0] == option) {
        args.erase(args.begin());
        return true;
    }
    return false;
}
bool GetBoolFromArg(std::vector<std::string> &args, const std::string &option, bool &value)
{
    if (MatchArgs(args, option)) {
        value = true;
        return true;
    } else {
        return false;
    }
}
} // namespace

USED_FUNCTION int main(int argc, char *argv[])
{
    std::vector<std::string> args;
    for (int i = 1; i < argc; i++) {
        args.push_back(argv[i]);
    }
    Option option;

    while (!args.empty()) {
        if (MatchArgs(args, "--help")) {
            Help();
            return 0;
        } else if (MatchArgs(args, "--boundcpu")) {
            if (!GetIntFromArg(args, option.boundCpu)) {
                return -1;
            }
        } else if (MatchArgs(args, "--sleep")) {
            if (!GetIntFromArg(args, option.sleepms)) {
                return -1;
            }
        } else if (MatchArgs(args, "--thread")) {
            if (!GetIntFromArg(args, option.numThreads)) {
                return -1;
            }
        } else if (MatchArgs(args, "--time")) {
            if (!GetIntFromArg(args, option.second)) {
                return -1;
            }
        } else if (MatchArgs(args, "--stack")) {
            if (!GetIntFromArg(args, option.stack)) {
                return -1;
            }
        } else if (GetBoolFromArg(args, "--dynamic", option.dynamicStack)) {
            continue;
        } else if (GetBoolFromArg(args, "--nowait", option.noWait)) {
            continue;
        } else if (GetBoolFromArg(args, "--mmap", option.mmap)) {
            continue;
        } else if (GetBoolFromArg(args, "--iowait", option.iowait)) {
            continue;
        } else if (GetBoolFromArg(args, "--branch", option.branch)) {
            continue;
        } else if (GetBoolFromArg(args, "--nonew", option.nonew)) {
            continue;
        } else if (GetBoolFromArg(args, "--nofunc", option.nofunc)) {
            continue;
        } else {
            printf("unknown format '%s'\n", args[0].c_str());
            return -1;
        }
    }

    if (option.boundCpu > -1) {
        cpu_set_t mask;
        CPU_ZERO(&mask);
        CPU_SET(option.boundCpu, &mask);
        if (sched_setaffinity(0, sizeof(cpu_set_t), &mask) == -1) {
            char errInfo[ERRINFOLEN] = { 0 };
            strerror_r(errno, errInfo, ERRINFOLEN);
            printf("Set CPU(%d) affinity failure, ERROR:%s\n", option.boundCpu, errInfo);
        }
    }
    if (!option.noWait) {
        WaitStart();
    }

    RunSampleThread(option);
    return 0;
}