#include "options.hpp"
#include "backend.hpp"
#include "daemon_backend.hpp"
#include "proxy_backend.hpp"
#include "log.hpp"
#include "thirdparty/CLI11.hpp"
#include "data.h"
#include "license.hpp"
#include <SDL.h>
#include "assets/assets.h"
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
#ifdef __HAIKU__
#include <os/AppKit.h>
#include <image.h>
#endif
#include "web_functions.hpp"
using namespace Looper;
using namespace Looper::Options;
using namespace Looper::Log;
#ifdef __EMSCRIPTEN__
extern "C" {
    void quit();
}
#endif
std::unordered_set<LicenseData> license_data;
std::unordered_set<LicenseData> &get_license_data() {
    return license_data;
}
char *executable_path;
#ifdef __ANDROID__
#include <SDL.h>
#include <jni.h>
extern jclass MainActivity;
extern jobject mainActivity;
extern jmethodID GetUserDir_Method;
extern jmethodID OpenFilePicker_Method;
extern jmethodID GetPickedFile_Method;
extern jmethodID ClearSelected_Method;
extern jmethodID IsLoading_Method;
extern JNIEnv *env;
void initNative() {
    MainActivity = reinterpret_cast<jclass>(env->NewGlobalRef(env->FindClass("com/complecwaft/looper/MainActivity")));
    GetUserDir_Method = env->GetStaticMethodID(MainActivity, "GetUserDir",
                                               "()Ljava/lang/String;");
    OpenFilePicker_Method = env->GetStaticMethodID(MainActivity, "OpenFilePicker",
                                             "(Ljava/lang/Object;)V");
    GetPickedFile_Method = env->GetStaticMethodID(MainActivity, "GetPickedFile",
                                                  "()Ljava/lang/String;");
    ClearSelected_Method = env->GetStaticMethodID(MainActivity, "ClearSelected", "()V");
    IsLoading_Method = env->GetStaticMethodID(MainActivity, "IsLoading", "()Z");
    jfieldID singleton = env->GetStaticFieldID(MainActivity, "mSingleton", "Lcom/complecwaft/looper/MainActivity;");
    mainActivity = reinterpret_cast<jobject>(env->NewGlobalRef(env->GetStaticObjectField(MainActivity, singleton)));
}
#endif
extern "C" int looper_run_as_executable(std::vector<std::string> args) {
#ifdef __ANDROID__
    env = (JNIEnv*)SDL_AndroidGetJNIEnv();
    initNative();
#endif
#ifdef __EMSCRIPTEN__
    EM_ASM({ Module.wasmTable = wasmTable; });
#endif
    CLI::App app{DESCRIPTION};
    std::string filename = "";
    app.allow_extras();
    std::string ui_backend_option = "";
    bool full_help = false;
    bool open_window = false;
    app.add_option("-l, --log-level", LogStream::log_level, "Sets the minimum log level to display in the logs.");
    app.add_option("-u, --ui-backend", ui_backend_option, "Specifies which UI backend to use.");
#ifdef DBUS_ENABLED
    bool daemonize = false;
    bool disable_gui = false;
    bool quit = false;
    app.add_flag("-d, --daemon", daemonize, "Daemonizes the program.");
    app.add_flag("-n, --no-gui", disable_gui, "Don't open the GUI when there is a daemon and there are settings or commands for it. Ignored in daemon mode, or when no changes in state are issued.");
    app.add_flag("-q, --quit", quit, "Quits an existing instance.");
#endif
    try {
        app.parse(args);
    } catch (const CLI::ParseError &e) {
        if (app.get_help_ptr()->get_callback_run()) {
            full_help = true;
        } else {
            exit(app.exit(e));
        }
    }
#ifdef DBUS_ENABLED
    if (daemonize) {
        ui_backend_option = "daemon";
    }
#endif
    args.clear();
    args = app.remaining(false);
    int new_argc = args.size();
    char **new_argv = (char**)malloc(new_argc * sizeof(char*));

#ifdef DBUS_ENABLED
    if (quit) {
        DBusAPISender *sender = DBusAPISender::Create();
        if (sender != nullptr) {
            sender->Quit();
        } else {
            ERROR.writeln("Did not find existing instance to quit.");
            return 1;
        }
        return 0;
    }
#endif
    {
        auto looper_mit = LicenseData("Looper (MIT)", "MIT");
        auto looper_gpl = LicenseData("Looper (GPL)", "GPL-3.0-or-later");
        auto looper = LicenseData("Looper", "MIT OR GPL-3.0-or-later");
        auto tomlpp = LicenseData("TOML++", "MIT");
        auto cli11 = LicenseData("CLI11", "BSD-3-Clause");
        auto jsoncpp = LicenseData("JsonCpp", "MIT");
        auto soundtouch = LicenseData("SoundTouch", "LGPL-2.1-only");
        LOAD_LICENSE(looper_mit, looper_mit);
        LOAD_LICENSE(looper_gpl, looper_gpl);
        LOAD_LICENSE(looper, looper);
        LOAD_LICENSE(tomlpp, tomlplusplus);
        LOAD_LICENSE(cli11, cli11);
        LOAD_LICENSE(jsoncpp, jsoncpp);
        LOAD_LICENSE(soundtouch, lgpl_2_1);
        license_data.insert(looper);
        license_data.insert(looper_gpl);
        license_data.insert(looper_mit);
        license_data.insert(tomlpp);
        license_data.insert(cli11);
        license_data.insert(jsoncpp);
        license_data.insert(soundtouch);
    }
    DEBUG.writeln("Command line arguments after first parse:");
    for (size_t i = 0; i < new_argc; i++) {
        auto &arg = args[i];
        DEBUG.writefln(" - '%s'", arg.c_str());
        new_argv[i] = strdup(arg.c_str());
    }
    DEBUG.writeln("Initializing frontends...");
    init_backends();
    init_playback_backends();
    init_audio_data();
#ifdef DBUS_ENABLED
    ProxyGlueBackend *proxy_backend = nullptr;
    if ((disable_gui && !daemonize) || quit) {
        if (!DBusAPISender::isOnlyInstance()) {
            UIBackend::register_backend<ProxyGlueBackend>();
            if (disable_gui) {
                proxy_backend = UIBackend::get_backend<ProxyGlueBackend>().value_or(nullptr);
            }
        }
        if (quit && proxy_backend == nullptr) {
            proxy_backend = UIBackend::get_backend<ProxyGlueBackend>().value_or(nullptr);
        }
    }
    if (daemonize) {
        UIBackend::register_backend<DaemonGlueBackend>();
    }
#endif
    for (auto kv : UIBackend::backends) {
        kv.second->add_licenses();
    }
    for (auto kv : PlaybackBackendHelper()) {
	    kv.second->add_licenses();
    }
    DEBUG.writeln("Loaded frontends: ");
    for (auto kv : UIBackend::backends) {
        DEBUG.writefln(" - '%s'", kv.first.c_str());
    }
    DEBUG.writeln("Loaded playback backends: ");
    for (auto kv : PlaybackBackendHelper()) {
        DEBUG.writefln(" - '%s'", kv.first.c_str());
    }
    DEBUG.writeln("Loading options file...");
    load_options();
    std::string backend_id = get_option<std::string>("ui.frontend", "imgui");
    UIBackend *backend = UIBackend::get_backend(ui_backend_option).value_or(UIBackend::get_backend(backend_id).value_or(UIBackend::get_first_backend()));
    int output = 0;
    if (backend == nullptr) {
        ERROR.writeln("No UI backend could be found.");
        return -1;
    } else {
        DEBUG.writefln("Using backend: '%s'...", backend->get_id().c_str());
        UIBackend::running_ui_backend = backend;
        if (full_help) {
            args.clear();
            args.push_back("--help");
        }
        try {
#ifdef DBUS_ENABLED
            if (proxy_backend != nullptr && !quit) {
                if (!proxy_backend->run(args, new_argc, new_argv)) {
                    throw 0;
                }
                proxy_backend = nullptr;
            }
            if (!quit) {
                UIBackend::unregister_backend<ProxyGlueBackend>();
            }
#endif
            output = backend->run(args, new_argc, new_argv);
#ifdef DBUS_ENABLED
            if (quit && proxy_backend != nullptr) {
                proxy_backend->quitDaemon();
                proxy_backend->unregister_self();
            }
#endif
        } catch (int return_code) {
            if (full_help) {
                std::string helpstr = app.help();
                helpstr = helpstr.substr(helpstr.find("\n") + 1);
                helpstr = helpstr.substr(helpstr.find("\n") + 1);
                helpstr = helpstr.substr(helpstr.find("\n") + 1);
                helpstr = helpstr.substr(helpstr.find("\n") + 1);
                helpstr = helpstr.substr(helpstr.find("\n") + 1);
                printf("%s", helpstr.c_str());
            }
            output = return_code;
        }
    }
    DEBUG.writeln("Exiting...");
    UIBackend::deinit_backends();
    for (int i = 0; i < new_argc; i++) {
        free(new_argv[i]);
    }
    free(new_argv);
    save_options();

#ifdef __EMSCRIPTEN__
    quit();
#endif
    return output;
}
std::string current_process_type;
extern int looper_run_playback_process(std::vector<std::string> args);
int main(int argc, char **argv) {
    if (argc == 0) {
        throw std::exception();
    }
#ifdef _WIN32
    size_t size = std::max(10240, PATH_MAX) + 1;
    executable_path = malloc(size);
    size = GetModuleFileNameA(NULL, executable_path, size);
    realloc(executable_path, size);
#elif defined(__HAIKU__)
	{
		int32 cookie = 0;
		image_info info;
		while (get_next_image_info(B_CURRENT_TEAM, &cookie, &info) == B_OK) {
			if (info.type == B_APP_IMAGE) {
				executable_path = strdup(info.name);
				break;
			}
		}
	}
#else
    executable_path = strdup(fs::canonical("/proc/self/exe").c_str());
#endif
    CLI::App app{DESCRIPTION};
    std::string process_type = "normal";
    app.add_option<std::string, std::string>("--process-type", process_type);
    app.add_option("-l, --log-level", LogStream::log_level, "Sets the minimum log level to display in the logs.");
    app.allow_extras();
    init_logging();
    try {
	    app.parse(argc, argv);
	    executable_path = argv[0];
	    current_process_type = "host";
	    if (process_type == "playback") {
	        current_process_type = "playback";
	        return looper_run_playback_process(app.remaining(false));
	    } else if (process_type == "daemon") {
	        std::vector<std::string> opts = app.remaining(false);
	        opts.push_back("-d");
	        return looper_run_as_executable(opts);
	    } else {
	        return looper_run_as_executable(app.remaining(false));
	    }
    } catch (CLI::CallForHelp) {
    	return looper_run_as_executable(std::vector<std::string>({"--help"}));
    }
    free(executable_path);
    return 255;
}