/*
 * 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 <chrono>
#include <unistd.h>
#include <cinttypes>
#include <memory>
#include <algorithm>
#include <iostream>
#include <getopt.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/statfs.h>
#include <typeinfo>
#include <cstring>
#include <vector>
#include <functional>
#include <atomic>
#include <mutex>
#include <ctime>
#include <condition_variable>
#include <cmath>
#include <string>
#include <vector>
#include <cmath>
#include <fcntl.h>
#include <cstdio>
#include <link.h>
#include <sys/mman.h>
#include "ipc_transactor.h"
#include "system_ui_controller.h"
#include "test_server_client.h"
#include "input_manager.h"
#include "i_input_event_consumer.h"
#include "pointer_event.h"
#include "ui_driver.h"
#include "ui_record.h"
#include "ui_input.h"
#include "ui_model.h"
#include "extension_executor.h"
#include "hisysevent.h"

using namespace std;
using namespace std::chrono;

namespace OHOS::uitest {
    const std::string HELP_MSG =
    "usage: uitest <command> [options]                                                                          \n"
    "help                                                                                    print help messages\n"
    "screenCap                                                                        capture the current screen\n"
    "  -p <savePath>                                                                      specifies the savePath\n"
    "  -d <displayId>                                                                       specifies the screen\n"
    "dumpLayout                                                               get the current layout information\n"
    "  -p <savePath>                                                                      specifies the savePath\n"
    "  -i                                                                     not merge windows and filter nodes\n"
    "  -a                                                                                include font attributes\n"
    "  -b <bundleName>                                             specifies the bundleName of the target window\n"
    "  -w <windowId>                                                specifies the window id of the target window\n"
    "  -m <true/false>          whether merge windows, true means to merge, set it true when not use this option\n"
    "  -d <displayId>                                           specifies the locate screen of the target window\n"
    "  -e <attributeName>                            extend by adding the specified attribute (uniqueId, accessibility)\n"
    "start-daemon <token>                                                                 start the test process\n"
    "uiRecord                                                                            recording Ui Operations\n"
    "  record                                                           Write Ui event information into csv file\n"
    "    -W <true/false>                                     whether save widget information, true means to save\n" 
    "                                                                       set it true when not use this option\n"
    "    -l                                             Save the current layout information after each operation\n"
    "    -c <true/false>              whether print the Ui event information to the console, true means to print\n" 
    "                                                                       set it true when not use this option\n"
    "  read                                                                    print file content to the console\n"
    "uiInput                                                                     inject Ui simulation operations\n"
    "  help                                                                                  print uiInput usage\n"
    "  dircFling  <direction> [velocity] [stepLength] [displayId]                                               \n"
    "                                                      direction ranges from 0,1,2,3 (left, right, up, down)\n"
    "  click/doubleClick/longClick <x> <y> [displayId]                           click on the target coordinates\n"
    "  swipe/drag <from_x> <from_y> <to_x> <to_y> [velocity] [displayId]                                        \n"
    "                                                             velocity ranges from 200 to 40000, default 600\n"
    "  fling <from_x> <from_y> <to_x> <to_y> [velocity] [stepLength] [displayId]                                \n"
    "                                                             velocity ranges from 200 to 40000, default 600\n"
    "  keyEvent <keyID/Back/Home/Power> [displayId]                                              inject keyEvent\n"
    "  keyEvent <keyID_0> <keyID_1> [keyID_2] [displayId]                               keyID_2 default to None \n"
    "  inputText <x> <y> <text> [displayId]                             inputText at the target coordinate point\n"
    "  text <text> [displayId]                               input text at the location where is already focused\n"
    "--version                                                                        print current tool version\n";

    const std::string VERSION = "7.1.0.1";
    struct option g_longoptions[] = {
        {nullptr, required_argument, nullptr, 'p'},
        {nullptr, required_argument, nullptr, 'd'},
        {nullptr, no_argument, nullptr, 'i'},
        {nullptr, no_argument, nullptr, 'a'},
        {nullptr, required_argument, nullptr, 'b'},
        {nullptr, required_argument, nullptr, 'w'},
        {nullptr, required_argument, nullptr, 'm'},
        {nullptr, required_argument, nullptr, 'e'},
        {nullptr, 0, nullptr, 0}};

    struct ReclaimContext {
        size_t pageSize = 0;
        size_t reclaimed = 0;
    };

    static int32_t PhdrReclaimCallback(struct dl_phdr_info *info, size_t, void *data)
    {
        if (info == nullptr || data == nullptr || info->dlpi_name == nullptr || info->dlpi_phdr == nullptr) {
            return 0;
        }
        // OpenHarmony .so naming convention: libxxx.z.so or libxxx.so, match suffix ".so"
        constexpr size_t soSuffixLen = 3; // strlen(".so")
        const char *name = info->dlpi_name;
        size_t nameLen = strlen(name);
        if (nameLen < soSuffixLen || memcmp(name + nameLen - soSuffixLen, ".so", soSuffixLen) != 0) {
            return 0;
        }
        auto *ctx = static_cast<ReclaimContext *>(data);
        for (uint16_t i = 0; i < info->dlpi_phnum; ++i) {
            const ElfW(Phdr) &phdr = info->dlpi_phdr[i];
            if (phdr.p_type != PT_LOAD) {
                continue;
            }
            if ((phdr.p_flags & PF_R) == 0 || (phdr.p_flags & PF_W) != 0) {
                continue;
            }
            auto start = static_cast<uintptr_t>(info->dlpi_addr + phdr.p_vaddr);
            auto alignedStart = start & ~(ctx->pageSize - 1);
            auto alignedEnd = (start + phdr.p_memsz + ctx->pageSize - 1) & ~(ctx->pageSize - 1);
            if (alignedStart >= alignedEnd) {
                continue;
            }
            if (madvise(reinterpret_cast<void *>(alignedStart), alignedEnd - alignedStart, MADV_DONTNEED) == 0) {
                ctx->reclaimed += alignedEnd - alignedStart;
            } else {
                int32_t savedErrno = errno;
                LOG_W("madvise failed: start=0x%{public}" PRIxPTR " len=%{public}zu errno=%{public}d",
                    alignedStart, alignedEnd - alignedStart, savedErrno);
            }
        }
        return 0;
    }

    static void ReclaimFileBackedPages() noexcept
    {
        ReclaimContext ctx;
        long ps = sysconf(_SC_PAGESIZE);
        if (ps <= 0) {
            LOG_W("sysconf(_SC_PAGESIZE) failed: %{public}ld", ps);
            return;
        }
        ctx.pageSize = static_cast<size_t>(ps);
        int32_t count = dl_iterate_phdr(PhdrReclaimCallback, &ctx);
        LOG_I("ReclaimFileBackedPages: iterated=%{public}d reclaimedBytes=%{public}zu", count, ctx.reclaimed);
    }

    /* *Print to the console of this shell process. */
    static inline void PrintToConsole(string_view message)
    {
        std::cout << message << std::endl;
    }

    static bool IsUiTestCreatedFile(const string fileName)
    {
        if (fileName.find("layout_") == 0 && fileName.rfind(".json") == fileName.size() - FIVE) {
            return true;
        }
        if (fileName.find("screenCap_") == 0 && fileName.rfind(".png") == fileName.size() - FOUR) {
            return true;
        }
        if (fileName == "record.csv") {
            return true;
        }
        return false;
    }

    static uint64_t GetFileSize(const string filePath)
    {
        struct stat fileStat;
        uint64_t fileSize = stat(filePath.c_str(), &fileStat) ? 0 : static_cast<uint64_t>(fileStat.st_size);
        return fileSize;
    }

    static uint64_t GetUiTestCreatedFileSize(const string newFilePath, string dirName)
    {
        uint64_t createdFileSize = 0;
        DIR* dir = opendir(dirName.c_str());
        if (!dir) {
            LOG_E("Open dir %{public}s failed.", dirName.c_str());
            return createdFileSize;
        }
        if (newFilePath != "") {
            createdFileSize += GetFileSize(newFilePath);
        }
        struct dirent* file;
        while ((file = readdir(dir)) != nullptr) {
            if (file->d_type == DT_REG && IsUiTestCreatedFile(file->d_name)) {
                string filePath = dirName + "/" + file->d_name;
                if (filePath != newFilePath) {
                    createdFileSize += GetFileSize(filePath);
                }
            }
        }
        closedir(dir);
        return createdFileSize;
    }

    static void ReportFileWriteEvent(string newFilePath)
    {
        string partitionName = "/data";
        string dirName = "/data/local/tmp";
        struct statfs partitionStat;
        if (statfs(partitionName.c_str(), &partitionStat) != 0) {
            LOG_E("Get remain partition size failed, partitionName = %{public}s", partitionName.c_str());
            return;
        }
        constexpr double units = 1024.0;
        double remainPartitionSize = (static_cast<double>(partitionStat.f_bfree) / units) *
                                     (static_cast<double>(partitionStat.f_bsize) / units);
        uint64_t createdFileSize = GetUiTestCreatedFileSize(newFilePath, dirName);
        vector<std::string> filePaths = { dirName };
        vector<uint64_t> fileSizes = { createdFileSize };
        HiSysEventWrite(HiviewDFX::HiSysEvent::Domain::FILEMANAGEMENT, "USER_DATA_SIZE",
            HiviewDFX::HiSysEvent::EventType::STATISTIC,
            "COMPONENT_NAME", "arkxtest",
            "PARTITION_NAME", partitionName,
            "REMAIN_PARTITION_SIZE", remainPartitionSize,
            "FILE_OR_FOLDER_PATH", filePaths,
            "FILE_OR_FOLDER_SIZE", fileSizes);
    }

    static int32_t GetParam(int32_t argc, char *argv[], string_view optstring, string_view usage,
        map<char, string> &params)
    {
        int opt;
        while ((opt = getopt_long(argc, argv, optstring.data(), g_longoptions, nullptr)) != -1) {
            switch (opt) {
                case '?':
                    PrintToConsole(usage);
                    return EXIT_FAILURE;
                case 'i':
                    params.insert(pair<char, string>(opt, "true"));
                    break;
                case 'a':
                    params.insert(pair<char, string>(opt, "true"));
                    break;
                case 'l':
                    params.insert(pair<char, string>(opt, "true"));
                    break;
                case 'c':
                case 'W':
                case 'm':
                    if (strcmp(optarg, "true") && strcmp(optarg, "false")) {
                        PrintToConsole("Invalid params");
                        PrintToConsole(usage);
                        return EXIT_FAILURE;
                    }
                    params.insert(pair<char, string>(opt, optarg));
                    break;
                default:
                    params.insert(pair<char, string>(opt, optarg));
                    break;
            }
        }
        return EXIT_SUCCESS;
    }

    static void DumpLayoutImpl(DumpOption &option, bool initController, ApiCallErr &err)
    {
        int32_t fd = open(option.savePath_.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
        if (fd == -1) {
            err = ApiCallErr(ERR_INVALID_INPUT, "Error path:" + string(option.savePath_) + strerror(errno));
            return;
        }
        if (initController) {
            auto controller = make_unique<SysUiController>();
#ifdef ARKXTEST_MULTI_USER_ENABLE
            int32_t userId = OHOS::testserver::TestServerClient::GetInstance()
                .GetUserIdByDisplayId(option.displayId_);
            if (userId != -1) {
                controller->SetActiveUser(userId);
            }
#endif
            UiDriver::RegisterController(move(controller));
        }
        auto driver = UiDriver();
        auto data = nlohmann::json();
        driver.DumpUiHierarchy(data, option, err);
        if (err.code_ != NO_ERROR) {
            close(fd);
            return;
        }
        string dumpStr = data.dump(-1, ' ', false, nlohmann::detail::error_handler_t::replace);
        LOG_D("dumpStr size = %{public}zu", dumpStr.size());
        ssize_t written = write(fd, dumpStr.c_str(), dumpStr.length());
        if (written == -1 || static_cast<size_t>(written) != dumpStr.length()) {
            LOG_E("Write dumpStr to file failed, errno: %{public}s", strerror(errno));
        }
        if (fsync(fd) == -1) {
            LOG_E("fsync failed, errno: %{public}s", strerror(errno));
        }
        close(fd);
        return;
    }

    static void StringSplit(const std::string &str, char split, std::vector<std::string> &res)
    {
        if (str.empty()) {
            return;
        }
        size_t start = 0;
        size_t pos = str.find(split);
        while (pos != std::string::npos) {
            if (pos > start) {
                res.emplace_back(str.substr(start, pos - start));
            }
            start = pos + 1;
            pos = str.find(split, start);
        }
        if (start < str.length()) {
            res.emplace_back(str.substr(start));
        }
    }

    static bool ParseExtendedAttrs(const string &attrsStr, DumpOption &option)
    {
        const vector<string> knownAttrs = {"uniqueId", "accessibility"};
        std::vector<std::string> amsAttrs;
        for (int i = static_cast<int>(UiAttr::ACCESSIBILITY_TEXT); i < static_cast<int>(UiAttr::MAX); ++i) {
            amsAttrs.emplace_back(std::string(ATTR_NAMES[i]));
        }
        std::vector<std::string> argvVec;
        StringSplit(attrsStr, ',', argvVec);
        for (auto &argv : argvVec) {
            if (std::find(knownAttrs.begin(), knownAttrs.end(), argv) == knownAttrs.end()) {
                PrintToConsole("Invalid attribute name, currently supported names are 'uniqueId, accessibility'.");
                return false;
            }
            if (argv == "accessibility") {
                for (const auto &attr : amsAttrs) {
                    option.extendedAttrs_ += attr + " ";
                }
            } else {
                option.extendedAttrs_ += argv + " ";
            }
        }
        return true;
    }

    static bool ParseDumpOption(const map<char, string> &params, DumpOption &option)
    {
        option.listWindows_ = params.find('i') != params.end();
        option.addExternAttr_ = params.find('a') != params.end();
        if (option.listWindows_ && option.addExternAttr_) {
            PrintToConsole("The -a and -i options cannot be used together.");
            return false;
        }
        auto iter = params.find('p');
        option.savePath_ = (iter != params.end()) ? iter->second : option.savePath_;
        auto iter2 = params.find('w');
        option.windowId_ = (iter2 != params.end()) ? iter2->second : "";
        auto iter3 = params.find('b');
        option.bundleName_ = (iter3 != params.end()) ? iter3->second : "";
        auto iter4 = params.find('m');
        option.notMergeWindow_ = (iter4 != params.end()) ? iter4->second == "false" : false;
        auto iter5 = params.find('d');
        option.displayId_ = (iter5 != params.end()) ? std::atoi(iter5->second.c_str()): 0;
        auto iter6 = params.find('e');
        if (iter6 != params.end()) {
            if (!ParseExtendedAttrs(iter6->second, option)) {
                return false;
            }
        }
        return true;
    }

    static int32_t DumpLayout(int32_t argc, char *argv[])
    {
        ReclaimFileBackedPages();
        DumpOption option;
        auto ts = to_string(GetCurrentMicroseconds());
        auto savePath = "/data/local/tmp/layout_" + ts + ".json";
        option.savePath_ = savePath;
        map<char, string> params;
        if (GetParam(argc, argv, "p:w:b:m:e:d:ia", HELP_MSG, params) == EXIT_FAILURE) {
            return EXIT_FAILURE;
        }
        if (!ParseDumpOption(params, option)) {
            return EXIT_FAILURE;
        }
        auto err = ApiCallErr(NO_ERROR);
        DumpLayoutImpl(option, true, err);
        if (err.code_ == NO_ERROR) {
            PrintToConsole("DumpLayout saved to:" + option.savePath_);
            ReportFileWriteEvent(option.savePath_);
            return EXIT_SUCCESS;
        } else if (err.code_ != ERR_INITIALIZE_FAILED) {
            PrintToConsole("DumpLayout failed:" + err.message_);
            return EXIT_FAILURE;
        }
        // Cannot connect to AAMS, broadcast request to running uitest-daemon if any
        err = ApiCallErr(NO_ERROR);
        auto cmd = OHOS::AAFwk::Want();
        cmd.SetParam("savePath", string(option.savePath_));
        cmd.SetParam("listWindows", option.listWindows_);
        cmd.SetParam("addExternAttr", option.addExternAttr_);
        cmd.SetParam("bundleName", string(option.bundleName_));
        cmd.SetParam("windowId", string(option.windowId_));
        cmd.SetParam("mergeWindow", option.notMergeWindow_);
        cmd.SetParam("displayId", to_string(option.displayId_));
        cmd.SetParam("extendedAttrs", string(option.extendedAttrs_));
        ApiTransactor::SendBroadcastCommand(cmd, err);
        if (err.code_ == NO_ERROR) {
            PrintToConsole("DumpLayout saved to:" + option.savePath_);
        } else {
            PrintToConsole("DumpLayout failed:" + err.message_);
        }
        return err.code_ == NO_ERROR ? EXIT_SUCCESS : EXIT_FAILURE;
    }

    static int32_t ScreenCap(int32_t argc, char *argv[])
    {
        auto ts = to_string(GetCurrentMicroseconds());
        auto savePath = "/data/local/tmp/screenCap_" + ts + ".png";
        auto displayId = 0;
        map<char, string> params;
        static constexpr string_view usage = "USAGE: uitest screenCap -p <path>";
        if (GetParam(argc, argv, "p:d:", usage, params) == EXIT_FAILURE) {
            return EXIT_FAILURE;
        }
        auto iter = params.find('p');
        if (iter != params.end()) {
            savePath = iter->second;
        }
        auto iter2 = params.find('d');
        if (iter2 != params.end()) {
            displayId = std::atoi(iter2->second.c_str());
        }
        auto controller = SysUiController();
        stringstream errorRecv;
        int32_t fd = open(savePath.c_str(), O_RDWR | O_CREAT, 0666);
        if (fd == -1) {
            PrintToConsole("ScreenCap failed: " + errorRecv.str());
            return EXIT_FAILURE;
        }
        fdsan_exchange_owner_tag(fd, 0, fdsan_create_owner_tag(FDSAN_OWNER_TYPE_FILE, LOG_DOMAIN));
        if (!controller.TakeScreenCap(fd, errorRecv, displayId)) {
            PrintToConsole("ScreenCap failed: " + errorRecv.str());
            return EXIT_FAILURE;
        }
        fdsan_close_with_tag(fd, fdsan_create_owner_tag(FDSAN_OWNER_TYPE_FILE, LOG_DOMAIN));
        PrintToConsole("ScreenCap saved to " + savePath);
        ReportFileWriteEvent(savePath);
        return EXIT_SUCCESS;
    }

    static string TranslateToken(string_view raw)
    {
        if (raw.find_first_of('@') != string_view::npos) {
            return string(raw);
        }
        return "default";
    }

    static DumpOption GetOptionForCmd(const OHOS::AAFwk::Want &cmd)
    {
        DumpOption option;
        option.savePath_ = cmd.GetStringParam("savePath");
        option.listWindows_ = cmd.GetBoolParam("listWindows", false);
        option.addExternAttr_ = cmd.GetBoolParam("addExternAttr", false);
        option.bundleName_ = cmd.GetStringParam("bundleName");
        option.windowId_ = cmd.GetStringParam("windowId");
        option.notMergeWindow_ = cmd.GetBoolParam("mergeWindow", true);
        option.displayId_ = atoi(cmd.GetStringParam("displayId").c_str());
        option.extendedAttrs_ = cmd.GetStringParam("extendedAttrs");
        return option;
    }

    static int32_t StartDaemon(string_view token, int32_t argc, char *argv[])
    {
        if (token.empty()) {
            LOG_E("Empty transaction token");
            return EXIT_FAILURE;
        }
        auto transalatedToken = TranslateToken(token);
        if (daemon(0, 0) != 0) {
            LOG_E("Failed to daemonize current process");
            return EXIT_FAILURE;
        }
        LOG_I("Server starting up");
        UiDriver::RegisterController(make_unique<SysUiController>());
        // accept remopte dump request during deamon running (initController=false)
        ApiTransactor::SetBroadcastCommandHandler([] (const OHOS::AAFwk::Want &cmd, ApiCallErr &err) {
            auto option = GetOptionForCmd(cmd);
            DumpLayoutImpl(option, false, err);
        });
        if (token == "singleness") {
            ExecuteExtension(VERSION, argc, argv);
            LOG_I("Server exit");
            ApiTransactor::UnsetBroadcastCommandHandler();
            _Exit(0);
            return 0;
        }
        ApiTransactor apiTransactServer(true);
        auto &apiServer = FrontendApiServer::Get();
        auto apiHandler = std::bind(&FrontendApiServer::Call, &apiServer, placeholders::_1, placeholders::_2);
        auto cbHandler = std::bind(&ApiTransactor::Transact, &apiTransactServer, placeholders::_1, placeholders::_2);
        apiServer.SetCallbackHandler(cbHandler); // used for callback from server to client
        if (!apiTransactServer.InitAndConnectPeer(transalatedToken, apiHandler)) {
            LOG_E("Failed to initialize server");
            ApiTransactor::UnsetBroadcastCommandHandler();
            _Exit(0);
            return EXIT_FAILURE;
        }
        mutex mtx;
        unique_lock<mutex> lock(mtx);
        condition_variable condVar;
        apiTransactServer.SetDeathCallback([&condVar]() {
            condVar.notify_one();
        });
        LOG_I("UiTest-daemon running, pid=%{public}d", getpid());
        condVar.wait(lock);
        LOG_I("Server exit");
        apiTransactServer.Finalize();
        ApiTransactor::UnsetBroadcastCommandHandler();
        _Exit(0);
        return 0;
    }

    static int32_t UiRecord(int32_t argc, char *argv[])
    {
        if (argc < THREE) {
            PrintToConsole("Missing parameter. \n");
            PrintToConsole(HELP_MSG);
            return EXIT_FAILURE;
        }
        std::string opt = argv[TWO];
        RecordOption option;
        if (argc >= FOUR) {
            if (strcmp(argv[THREE], "point") == 0) {
                option.saveWidget = false;
            }
        }
        if (opt == "record") {
            map<char, string> params;
            if (GetParam(argc, argv, "W:c:l", HELP_MSG, params) == EXIT_FAILURE) {
                return EXIT_FAILURE;
            }
            option.saveLayout = params.find('l') != params.end();
            auto iter = params.find('c');
            option.terminalCout = (iter != params.end()) ?iter->second == "true" : true;
            if (option.saveWidget) {
                auto w = params.find('W');
                option.saveWidget = (w != params.end()) ?w->second == "true" : true;
            }
            auto controller = make_unique<SysUiController>();
            ApiCallErr error = ApiCallErr(NO_ERROR);
            if (option.saveWidget || option.saveLayout) {
                if(!controller->ConnectToSysAbility(error)){
                    PrintToConsole(error.message_);
                    return EXIT_FAILURE;
                }
            }
            UiDriver::RegisterController(move(controller));
            ReportFileWriteEvent("");
            return UiDriverRecordStart(option);
        } else if (opt == "read") {
            EventData::ReadEventLine();
            return OHOS::ERR_OK;
        } else {
            PrintToConsole("Illegal argument: " + opt);
            PrintToConsole(HELP_MSG);
            return EXIT_FAILURE;
        }
    }

    static int32_t UiInput(int32_t argc, char *argv[])
    {
        if (argc < FOUR) {
            std::cout << "Missing parameter. \n" << std::endl;
            PrintInputMessage();
            return EXIT_FAILURE;
        }
        if ((string)argv[THREE] == "help") {
            PrintInputMessage();
            return OHOS::ERR_OK;
        }
        auto controller = make_unique<SysUiController>();
        UiDriver::RegisterController(move(controller));
        return UiActionInput(argc, argv);
    }

    extern "C" int32_t main(int32_t argc, char *argv[])
    {
        if (argc < TWO) {
            PrintToConsole("Missing argument");
            PrintToConsole(HELP_MSG);
            _Exit(EXIT_FAILURE);
        }
        string command(argv[1]);
        if (command == "dumpLayout") {
            _Exit(DumpLayout(argc, argv));
        } else if (command == "start-daemon") {
            string_view token = argc < 3 ? "" : argv[2];
            _Exit(StartDaemon(token, argc - THREE, argv + THREE));
        } else if (command == "screenCap") {
            _Exit(ScreenCap(argc, argv));
        } else if (command == "uiRecord") {
            _Exit(UiRecord(argc, argv));
        } else if (command == "uiInput") {
            _Exit(UiInput(argc, argv));
        } else if (command == "--version") {
            PrintToConsole(VERSION);
            _Exit(EXIT_SUCCESS);
        } else if (command == "help") {
            PrintToConsole(HELP_MSG);
            _Exit(EXIT_SUCCESS);
        } else {
            PrintToConsole("Illegal argument: " + command);
            PrintToConsole(HELP_MSG);
            _Exit(EXIT_FAILURE);
        }
    }
}