#include "pch_msc.hpp"

#include "libaquery.h"
#include <cstdio>
#include <string>
#include "monetdb_conn.h"
#include "monetdbe.h"

#include "table.h"
#include <thread>

#ifdef _WIN32
    #include "winhelper.h"
#else 
    #include <dlfcn.h>
    #include <fcntl.h>
    #include <sys/mman.h>
    #include <atomic>
#endif // _WIN32

#ifdef ERROR
    #undef ERROR
#endif
#ifdef static_assert
    #undef static_assert
#endif

constexpr const char* monetdbe_type_str[] = {
	"monetdbe_bool", "monetdbe_int8_t", "monetdbe_int16_t", "monetdbe_int32_t", "monetdbe_int64_t",
#ifdef HAVE_HGE
	"monetdbe_int128_t",
#endif
	"monetdbe_size_t", "monetdbe_float", "monetdbe_double",
	"monetdbe_str", "monetdbe_blob",
	"monetdbe_date", "monetdbe_time", "monetdbe_timestamp",

	// should be last:
	"monetdbe_type_unknown"
} ;

inline constexpr static unsigned char monetdbe_type_szs[] = {
    sizeof(monetdbe_column_bool::null_value), sizeof(monetdbe_column_int8_t::null_value), 
    sizeof(monetdbe_column_int16_t::null_value), sizeof(monetdbe_column_int32_t::null_value), 
    sizeof(monetdbe_column_int64_t::null_value),
#ifdef HAVE_HGE
    sizeof(monetdbe_column_int128_t::null_value),
#endif
    sizeof(monetdbe_column_size_t::null_value), sizeof(monetdbe_column_float::null_value),
    sizeof(monetdbe_column_double::null_value),
    sizeof(monetdbe_column_str::null_value), sizeof(monetdbe_column_blob::null_value),
    sizeof(monetdbe_data_date), sizeof(monetdbe_data_time), sizeof(monetdbe_data_timestamp),

    // should be last:
    1
};

namespace types{
    constexpr const Type_t monetdbe_type_aqtypes[] = {
        ABOOL, AINT8, AINT16, AINT32, AINT64, 
#ifdef HAVE_HGE
        AINT128,
#endif
        AUINT64, AFLOAT, ADOUBLE, ASTR, 
        // blob?
        AINT64,
        ADATE, ATIME, ATIMESTAMP, ERROR

    };
}

Server::Server(Context* cxt){
    if (cxt){
        connect(cxt);
    } 
}

void Server::connect(Context *cxt){
    auto server = static_cast<monetdbe_database*>(this->server);
    if (cxt){
        cxt->alt_server = this;
        this->cxt = cxt;
    }
    else{
        puts("Error: Empty context.");
        return;
    }

    if (server){
        printf("Error: Server %p already connected. Restart? (Y/n). \n", server);
        char c[50];
        std::cin.getline(c, 49);
        for(int i = 0; i < 50; ++i) {
            if (!c[i] || c[i] == 'y' || c[i] == 'Y') {
                monetdbe_close(*server);
                free(*server);
                this->server = nullptr;
                break;
            }
            else if(c[i]&&!(c[i] == ' ' || c[i] == '\t'))
                break;
        }
    }

    server = (monetdbe_database*)malloc(sizeof(monetdbe_database));
    monetdbe_options ops;
    AQ_ZeroMemory(ops);
    ops.nr_threads = std::thread::hardware_concurrency();
    auto ret = monetdbe_open(server, nullptr, &ops);
    if (ret == 0){
        status = true;
        this->server = server;
    }
    else{
        if(server)
            free(server);
        this->server = nullptr;
        status = false;
        puts(ret == -1 ? "Allocation Error." : "Internal Database Error.");
    }
}

void Server::exec(const char* q){
    auto server = static_cast<monetdbe_database*>(this->server);
    auto _res = static_cast<monetdbe_result*>(this->res);
    monetdbe_cnt _cnt = 0;
    auto qresult = monetdbe_query(*server, const_cast<char*>(q), &_res, &_cnt);
    if (_res != nullptr){
        this->cnt = _res->nrows;
        this->res = _res;
    }
    if (qresult != nullptr){
        printf("Execution Failed. %s\n", qresult);
        last_error = qresult;
    }
}

bool Server::haserror(){
    if (last_error){
        last_error = nullptr;
        return true;
    }
    else{
        return false;
    }
}


void Server::print_results(const char* sep, const char* end){

    if (!haserror()){
        auto _res = static_cast<monetdbe_result*> (res);
        const auto& ncols = _res->ncols;
        monetdbe_column** cols = static_cast<monetdbe_column**>(malloc(sizeof(monetdbe_column*) * ncols));
        std::string* printf_string = new std::string[ncols];
        const char** col_data = static_cast<const char**> (malloc(sizeof(char*) * ncols));
        uint8_t* szs = static_cast<uint8_t*>(alloca(ncols));
        std::string header_string = "";
        const char* err_msg = nullptr;
        for(uint32_t i = 0; i < ncols; ++i){
            err_msg = monetdbe_result_fetch(_res, &cols[i], i);
            printf_string[i] = 
                std::string(types::printf_str[types::monetdbe_type_aqtypes[cols[i]->type]]) 
                + (i < ncols - 1 ? sep : "");
            puts(printf_string[i].c_str());
            puts(monetdbe_type_str[cols[i]->type]);
            col_data[i] = static_cast<char *>(cols[i]->data);
            szs [i] = monetdbe_type_szs[cols[i]->type];
            header_string = header_string + cols[i]->name + sep + '|' + sep;
            if (err_msg) [[unlikely]]
                puts(err_msg);
        }
		if (const size_t l_sep = strlen(sep) + 1; header_string.size() >= l_sep)
			header_string.resize(header_string.size() - l_sep);
        header_string += end + std::string(header_string.size(), '=') + end;
        fputs(header_string.c_str(), stdout);
        for(uint64_t i = 0; i < cnt; ++i){
            for(uint32_t j = 0; j < ncols; ++j){
                printf(printf_string[j].c_str(), *((void**)col_data[j]));
                col_data[j] += szs[j];
            }
            fputs(end, stdout);
        }
        free(cols);
        delete[] printf_string;
        free(col_data);
    }
}

void Server::close(){
    if(this->server){
        auto server = static_cast<monetdbe_database*>(this->server);
        monetdbe_close(*server);
        free(server);
        this->server = nullptr;
    }
}

void* Server::getCol(int col_idx){
    if(res){
        auto _res = static_cast<monetdbe_result*>(this->res);
        auto err_msg = monetdbe_result_fetch(_res, 
            reinterpret_cast<monetdbe_column**>(&ret_col), col_idx);
        if(err_msg == nullptr)
        {
            auto _ret_col = static_cast<monetdbe_column*>(this->ret_col);
            cnt = _ret_col->count;
             printf("Dbg: Getting col %s, type: %s\n", 
                _ret_col->name, monetdbe_type_str[_ret_col->type]);
            return _ret_col->data;
        }
        else{
            printf("Error fetching result: %s\n", err_msg);
        }
    }
    else{
        puts("Error: No result.");
    }
    return nullptr;
}

Server::~Server(){
    close();
}

bool Server::havehge() {
#if defined(_MONETDBE_LIB_) and defined(HAVE_HGE)
    // puts("true");
    return HAVE_HGE;
#else
    // puts("false");
    return false;
#endif
}


void ExecuteStoredProcedureEx(const StoredProcedure *p, Context* cxt){
    auto server = static_cast<Server*>(cxt->alt_server);
    void* handle = nullptr;
    uint32_t procedure_module_cursor = 0;
    for(uint32_t i = 0; i < p->cnt; ++i) {
        switch(p->queries[i][0]){
            case 'Q': {
                server->exec(p->queries[i]);
            } 
            break;
            case 'P': {
                auto c = code_snippet(dlsym(handle, p->queries[i]+1));
                c(cxt);
            }
            break;
            case 'N': {
                if(procedure_module_cursor < p->postproc_modules)
                    handle = p->__rt_loaded_modules[procedure_module_cursor++];
            }
            break;
            case 'O': {
                uint32_t limit;
                memcpy(&limit, p->queries[i] + 1, sizeof(uint32_t));
                if (limit == 0)
                    continue;
                print_monetdb_results(server, " ", "\n", limit); 
            }
            break;
            default:
                printf("Warning Q%u: unrecognized command %c.\n", 
                    i, p->queries[i][0]);
        }
    }
}

int execTriggerPayload(void* args) {
    auto spp = (StoredProcedurePayload*)(args);
    ExecuteStoredProcedureEx(spp->p, spp->cxt);
    delete spp;
    return 0;
}
