#include <sstream>

#include "util.h"
#include "config.h"
#include "logger.h"
#include "base64.h"
#include "file.h"
#include <cstring>

using namespace std;

#ifdef WIN32
    bool is_executable(const std::string& file) {
        DWORD lpBinaryType[100];
        return GetBinaryTypeA(file.c_str(), lpBinaryType);
    }

    VOID execute_app(LPCTSTR lpApplicationName,LPSTR arguments)
    {
        // additional information
        STARTUPINFO si;
        PROCESS_INFORMATION pi;

        // set the size of the structures
        ZeroMemory( &si, sizeof(si) );
        si.cb = sizeof(si);
        ZeroMemory( &pi, sizeof(pi) );

        // start the program up
        auto result = CreateProcess( lpApplicationName,   // the path
                       arguments,        // Command line
                       nullptr,           // Process handle not inheritable
                       nullptr,           // Thread handle not inheritable
                       FALSE,          // Set handle inheritance to FALSE
                       CREATE_NEW_CONSOLE  ,              // No creation flags
                       nullptr,           // Use parent's environment block
                       nullptr,           // Use parent's starting directory
                       &si,            // Pointer to STARTUPINFO structure
                       &pi             // Pointer to PROCESS_INFORMATION structure (removed extra parentheses)
        );
        // Close process and thread handles.
        CloseHandle( pi.hProcess );
        CloseHandle( pi.hThread );
    }
#else
    #include <unistd.h>
    bool is_executable(const std::string& file) {
        return access(file.c_str(), F_OK | X_OK) == 0;
    }

    typedef const char* LPSTR;
    typedef const char* LPCTSTR;

    void execute_app(LPCTSTR, LPSTR app_arguments) {
        auto arguments_length = strlen(app_arguments);

        auto buffer_size = arguments_length + 512;
        auto buffer = (char*) malloc(buffer_size);

        memcpy(buffer, app_arguments, arguments_length);
        memcpy(buffer + arguments_length, " &", 2);

        system(app_arguments);
        free(buffer);
    }
#endif

extern std::string log_file_path;
inline std::string build_callback_info(const std::string& error_id, const std::string& error_message) {
    stringstream ss;

    ss << ";log_file:" << base64::encode(log_file_path);
    if(!error_id.empty()) {
        ss << ";error_id:" << base64::encode(error_id);
        ss << ";error_message:" << base64::encode(error_message);
    }
    auto result = ss.str();
    if(result.length() > 0)
        result = result.substr(1);
    return base64::encode(result);
}

void execute_callback_fail_exit(const std::string& error, const std::string& error_message) {
    file::rollback();
    
    if(!is_executable(config::callback_file)) {
        logger::fatal("callback file (%s) is not executable! Ignoring fail callback", config::callback_file.c_str());
        logger::flush();
        exit(1);
    }

    auto cmd_line = config::callback_file + " " + config::callback_argument_fail + build_callback_info(error, error_message);
    logger::info("executing callback file %s with fail command line %s", config::callback_file.c_str(), cmd_line.c_str());
	logger::flush();
    execute_app((LPCTSTR) config::callback_file.c_str(), (LPSTR) cmd_line.c_str());
    exit(1);
}

void execute_callback_success_exit() {
    if(!is_executable(config::callback_file)) {
        logger::fatal("callback file (%s) is not executable! Ignoring success callback", config::callback_file.c_str());
        logger::flush();
        exit(1);
    }

    auto cmd_line = config::callback_file + " " + config::callback_argument_success + build_callback_info("", "");
    logger::info("executing callback file %s with success with command line %s", config::callback_file.c_str(), cmd_line.c_str());
	logger::flush();
    execute_app((LPCTSTR) config::callback_file.c_str(), (LPSTR) cmd_line.c_str());
    exit(0);
}

#ifdef WIN32
bool is_administrator() {
    bool result{false};
    HANDLE token_handle{nullptr};
    if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token_handle)) {
        TOKEN_ELEVATION eval{};
        DWORD eval_size = sizeof(TOKEN_ELEVATION);
        if (GetTokenInformation(token_handle, TokenElevation, &eval, eval_size, &eval_size)) {
            result = eval.TokenIsElevated;
        }
    }

    if (token_handle)
        CloseHandle(token_handle);

    return result;
}

extern bool request_administrator(int argc, char** argv) {
    char szPath[MAX_PATH];
    if (GetModuleFileName(nullptr, szPath, ARRAYSIZE(szPath))) {
        SHELLEXECUTEINFO sei = { sizeof(sei) };

        sei.lpVerb = "runas";
        sei.lpFile = szPath;
        sei.hwnd = nullptr;
        sei.nShow = SW_NORMAL;
        
        if(argc > 1) {
            size_t param_size = 0;
            for(int i = 1; i < argc; i++)
                param_size += strlen(argv[i]) + 1;
            sei.lpParameters = (char*) malloc(param_size);
            if(!sei.lpParameters) return false;

            auto buf = (char*) sei.lpParameters;
            for(int i = 1; i < argc; i++) {
                const auto length = strlen(argv[i]);
                memcpy(buf, argv[i], length);
                buf += length;
                *buf++ = ' ';
            }
            *(--buf) = 0;
        }
        
        bool success = ShellExecuteEx(&sei);
        if(sei.lpParameters) ::free((void*) sei.lpParameters);
        return success;
    }
    return true;
}
#endif